How to create an entity when another is updating - api

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!

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 save 'department id' and 'user id' of 'current logged in user' while saving a record in asp.net core 3.0+?

Previously in asp.net webforms I was used to save current user id, their department id, branch id (in case of multiple branches of organisation), branch name etc in cookies and was using this information to avoid extra server calls while saving, updating, retrieving or deleting record and also for tracking that who deleted, updated or created a specific record. But now in asp.net core 2.0, 3.0+ I wander how to handle this thing. I am thinking to handle this by creating claim and saving this information in claims and then using it for good. Am I doing it wisely or Is there any other effective/efficient way of doing this whole practice ? Can I use JWT for this purpose or not ?
You can create a class (e.g.,) MyUserClaimsFactory that inherits from UserClaimsPrincipalFactory<User, Role> where User and Role are you custom classes that derive from IdentityUser and IdentityRole.
In that class, override the CreateAsync method
public async override Task<ClaimsPrincipal> CreateAsync(User user)
{
var principal = await base.CreateAsync(user);
// Add all the claims you need here.
((ClaimsIdentity)principal.Identity).AddClaims(new[] { new Claim(ClaimTypes.GivenName, user.Name) });
return principal;
}
Instead of ClaimTypes you can also use a string as key.
Add the class via dependency injection:
services.AddScoped<IUserClaimsPrincipalFactory<User>, MyUserClaimsFactory>();
You can retrieve the claims in a Controller via this.User.Identity
var givenName = (this.User.Identity as ClaimsIdentity).FirstOrNull(ClaimTypes.GivenName);

Laravel 5 - creating two tables during registration

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()

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.

How can I update user data form session in ServiceStack?

Very simple question: I have a session object in my Service:
var session = this.GetSession(); //IAuthSession
if (!session.IsAuthenticated)
I can modify some values in the session class (e.g. Permissions) based on the parameters passed to the service; then I want to save them.
How?
The direct way of doing it: create a UserAuth object, popolate it with all the fields from IAuthSession, get the IDbConnectionFactory, save it.
Surely there is a faster and better way, but I was not able to find it!
More generally, how can I switch between IAuthSession anf UserAuth? I.e., given a IAuthSession object, how can I obtain a UserAuth object, modify it, and persist the modifications?
I have read this question on how to append metadata to a user login info, but something is still missing.
Once you have added what you need, how do you save it? (I doubt you just add the metadata to both session and UserAuth, and then you use IDbConnectionFactory to save the latter; there must be a better way!)
Old question but worth answering.
The UserAuthRepository being used should have an implementation of the UpdateUserAuth method that can be called to save the UserAuth changes
UpdateUserAuth(UserAuth existingUser, UserAuth newUser, string password)
Another easier way would be to just call the RegisterService using PUT which will update the existing registered user for you.
/// <summary>
/// Update an existing registraiton
/// </summary>
public object Put(Register request)
{
return Post(request);
}
The service call would be something similar to this:
using (var authService = base.ResolveService<RegisterService>())
{
var authResponse = authService.Put(
new Register {
UserName = session.UserName ?? session.Email,
Email = session.Email,
etc...
});
if (authResponse is IHttpError)
throw (Exception)authResponse;
}