symfony : best way to set a user with a relationship - symfony

I'm very new in development and symfony. I wonder me what's the best way to code this below.
I've 2 entities (user and account). There is a relation between them (create an account requiers a user).
I wonder me what is the best way to set the user in account entity (prepersist, controller, __construct) when I'm adding an new account ?
PREPERSIST
First, I didn't find anything to set the user with prepersit method. Is there a way ?
Something like that :
/**
* #ORM\PrePersist
*/
public function prePersist()
{
$this->user = $this->get('security.context')->getToken()->getUser();
$this->updatedAt = new \Datetime("now");
$this->isActive = false;
}
CONTROLLER
...
$user = new User();
$account = new Account();
$account->setUser($user);
...
CONSTRUCTOR
/* Entity account */
...
public function __construct($user)
{
$this->user = $user;
}
...
/* Controller account */
...
$account = new Account($this->get('security.context')->getToken()->getUser())
...
Hope you can help me.

Based on your code above, you don't need to hook into a doctrine event to accomplish what you want. You can create the association in the controller before persisting the Account object.
If you are using the Symfony security component, obtaining the user in the controller is as simple as $this->getUser(). You can inject User via the Account constructor method __construct($user) or a setter method setUser($user). Although the constructor method is more efficient, either way is correct.
And to persist the Account object to your database from within the controller:
$em = $this->getDoctrine()->getManager();
$em->persist($account);
$em->flush();

I would recommend creating Doctrine2 Listener / Subscriber and register it as a service, than listen to prePersist event of Account entity. You can inject any other needed services in listeners / subscribers.
All information you need can be found on: http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html

Related

Custom decision manager authorisation in Symfony 4

I have a specific authorisation system in my application (asked by my managers). It is based on Joomla. Users are attached to usergroups. Every action (i.e page) in my application are resources and for each resources I have set an access level. I have then to compare the resource access level with the usergroups of the current user to grant access or not to this specific resource.
All those informations are stored in database which are in return entities in Symfony :
User <- ManyToMany -> Usergroups
Menu (all resources with path and access level)
I thought about the Voter system. It is kind alike of what I would want, I think. Can I hijack the support function for this ?
protected function supports($user, $resource)
{
//get usergroups of the $user => $usergroups
//get the access level of the resource => $resource_access
// if the attribute isn't one we support, return false
if (!in_array($usergroups, $resource_access)) {
return false;
}
return true;
}
The get the usergroups and the access level of the resource I will have to do some queries in the database. To use this, then I would to use the denyAccessUnlessGranted() function in all my controller (seems redundant by the way) ?
Do you think it would work or there is another system more suited for this case ? I thought of doing the control in a listener to the kernel.request event too.
Hope I am clear enough, I'm new to symfony and still have some issues to understand how everything are related and working.
The voter component should be a good fit for this, as its a passive approach that lets you implement any logic in a way where its fixable through code, without modifying any database specific acl tree not managed by symfony itself.
Voters are called if you use denyAccessUnlessGranted() or isGranted() either through code, annotation or twig.
Lets take a look at how you want to check if the current user has access to view the index page:
class SomeController {
public function index() {
$this->denyAccessUnlessGranted('VIEW', '/index');
// or use some magic method to replace '/index' with wathever you require,
// like injecting $request->getUri(), just make sure your voter can
// parse it quickly.
// ...
}
}
Now build the a very simple voter:
class ViewPageVoter extends Voter
{
/**
* #var EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
protected function supports($attribute, $subject)
{
return is_string($subject) && substr($subject, 0, 1) === '/';
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$currentUser = $token->getUser();
if(!$currentUser) {
// no user or authentication, deny
return false;
}
// Do the query to see if the user is allowed to view the resource.
// $this->em->getRepository(...) or
// $this->em->getConnection()
//
// $attribute = VIEW
// $subject = '/index'
// $currentUser = authenticated user
// return TRUE if allowed, return FALSE if not.
}
}
As a nice bonus you can easily see additional details on security voters in the /_profiler of that request, also indicating their respective vote on the subject.

Symfony 2 - entity repository - accesing entity data from repository

I have user entity with custom repository for it. I have created an event listener to check if given user is authorized to access given game account.
To this I have created custom function hasAccountOfId($id) in my user repository. However I have problem with accessing this function from the event listener.
User entity:
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository")
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/.../
/**
* #ORM\OneToMany(targetEntity="Account", mappedBy="user")
*/
protected $accounts;
User entity custom repository:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
/**
* UserRepository
*/
class UserRepository extends EntityRepository
{
public function hasAccountOfId($accountId)
{
foreach ($this->accounts as $account) {
if ($account.id == $accountId) {
return true;
}
}
return false;
}
}
And the listener where I am struggling with accessing hasAccountOfId function.
// Account is set, verify if user is authorized
$id = $this->session->get("accountId");
$user = $this->token_storage->getToken()->getUser();
$userEm = $this->em->getRepository('AppBundle:User');
if(!$user->hasAccountOfId($id))
{
// Not authorized
die("Not authorized");
}
returns:
Attempted to call an undefined method named "hasAccountOfId" of class
"AppBundle\Entity\User".
while using !$userEm->hasAccountOfId($id) results in:
Notice: Undefined property: AppBundle\Entity\UserRepository::$accounts
How may I access my user repository for $user = $this->token_storage->getToken()->getUser() ?
Firstly for this simple OneToMany association You need to provide the Owning side of relation (in Account class). By creating new Account for User You will need to call $account->setUser($user); to make the Relation.
Secondly You don't need a custom method in Your RepositoryClass as You will get Accounts from Doctrine's Relation: $user->getAccounts().
The iteration through the Accounts to look for ID should be done NOT in RepositoryClass or Entity itself, but using a Manager Service or something else, because other way will violate Singe Responsibility Principle.
And finally, there is no . for class properties or methods, it must be ->. Note if statement in hasAccountOfId method.

Symfony FOSUserBundle - Create custom repository to persist users

I am using FOSUserBundle to be able to manage users in a symfony2 project.
Since using container is not recommended my question is how can I extend the FOSUserBundle to be able to create a custom save method like this, for example:
class UserRepository extends EntityRepository
{
public function registration(array $data)
{
// example only
$userManager = $this->container->get('fos_user.user_manager');
//$em = $this->container->get('doctrine')->getEntityManager();
//$userUtils = $this->container->get('fos_user.util.token_generator');
$user = $userManager->createUser();
$user->setFirstName($data['first_name']);
$user->setLastName($data['last_name']);
$user->setEmail($data['user_email']);
$user->setUsername($data['user_email']);
$user->setPlainPassword($data['user_password']);
$user->setEnabled(false);
$user->setConfirmationToken($userUtils->generateToken());
$user->addRole('ROLE_USER');
$em->persist($user);
$em->flush();
}
Would it be smart to pass the $userManager and $userUtils objects in the controller when using the method?
I think the better is to override the FosUser Controller Action (Register for example) and put your code in a specific service.
The symfony2 doc give a great sample: http://symfony.com/doc/current/cookbook/bundles/inheritance.html

How to a dynamically add roles to a user?

When a user logs in, whether it be for the first time or via a cookie that's present, I want to assign them one or more additional user roles based on the result of some queries I run.
I think I need to create an Event Listener that gets the security component and entity manager injected, so I can run my queries and add roles to the current user.
I'm not quite sure if this is possible with events though, since the event would need to be fired within the Firewall context before the authorization is done, but after authentication.
I did see this existing question, but I can't get it to work (the roles are not actually recognized after the event is run).
Is it possible to do this with an event listener?
I think my alternative would be to use a postload lifecycle callback on the User entity and run some queries there, but that doesn't seem right.
You could create an event listener on the kernel. It will run everytime a page is loaded.
It will check if their is a logged in user and then you can do some custom logic to see if you need to update their role, if you do then update it log them in with the new settings and then they'll continue in the system with their new role.
I haven't tested this code, so it might have some bugs.
services.yml
bundle.eventlistener.roles:
class: Sample\MyBundle\EventListener\Roles
arguments: [#service_container, #security.context]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Listener\Roles.php
namespace Sample\MyBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Sample\MyBundle\Entity\Users; //Your USER entity
class Roles {
private $container;
private $context;
public function __construct(Container $container, SecurityContext $context) {
$this->container = $container;
$this->context = $context;
}
public function onKernelController(FilterControllerEvent $event) {
if($this->context->getToken()->getUser() instanceof Users) {
//Custom logic to see if you need to update the role or not.
$user = $this->context->getToken()->getUser();
//Update your roles
$user->setRole('ROLE_WHATEVER');
$em = $this->container->get('doctrine')->getManager();
$em->persist($user);
$em->flush();
//Create new user token
//main == firewall setting
$token = new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles());
$this->context->setToken($token);
}
}
}

FOSUserBundle: Get EntityManager instance overriding Form Handler

I am starting with Symfony2 and I am trying to override FOS\UserBundle\Form\Handler\RegistrationFormHandler of FOSUserBundle.
My code is:
<?php
namespace Testing\CoreBundle\Form\Handler;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Form\Handler\RegistrationFormHandler as BaseHandler;
use Testing\CoreBundle\Entity\User as UserDetails;
class RegistrationFormHandler extends BaseHandler
{
protected function onSuccess(UserInterface $user, $confirmation)
{
// I need an instance of Entity Manager but I don't know where get it!
$em = $this->container->get('doctrine')->getEntityManager();
// or something like: $em = $this->getDoctrine()->getEntityManager
$userDetails = new UserDetails;
$em->persist($userDetails);
$user->setId($userDetails->getId());
parent::onSuccess($user, $confirmation);
}
}
So, the point is that I need an instance of Doctrine's Entity Manager but I don't know where/how get it in this case!
Any idea?
Thanks in advance!
You should not use EntityManager directly in most of the cases. Use a proper manager/provider service instead.
In case of FOSUserBundle service implementing UserManagerInterface is such a manager. It is accessible through fos_user.user_manager key in the service container (which is an allias to fos_user.user_manager.default). Of course registration form handler uses that service, it is accessible through userManager property.
You should not treat your domain-model (i.a. Doctrine's entities) as if it was exact representation of the database-model. This means, that you should assign objects to other objects (not their ids).
Doctrine is capable of handling nested objects within your entities (UserDetails and User objects have a direct relationship). Eventually you will have to configure cascade options for User entity.
Finally, UserDetails seems to be a mandatory dependency for each User. Therefore you should override UserManagerInterface::createUser() not the form handler - you are not dealing with user's details there anyway.
Create your own UserManagerInterface implementation:
class MyUserManager extends \FOS\UserBundle\Entity\UserManager {
/**
* {#inheritdoc}
*/
public function createUser() {
$user = parent::createUser();
$user->setUserDetails(new UserDetails());
// some optional code required for a proper
// initialization of User/UserDetails object
// that might require access to other objects
// not available inside the entity
return $user;
}
}
Register your own manager as a serive inside DIC:
<service id="my_project.user_manager" class="\MyProject\UserManager" parent="fos_user.user_manager.default" />
Configure FOSUserBundle to use your own implementation:
# /app/config/config.yml
fos_user:
...
service:
user_manager: my_project.user_manager

Resources