Erica Ackerman
Erica Ackerman

Reputation: 199

How can you make a Symfony user provider execute a method on an EntityRepository?

I have a Symfony 2 application that uses the Cosign single-sign-on solution for authentication (https://github.com/fmfi-svt/cosign-bundle), and then uses a custom User Provider to set roles and to handle authorization on any particular route. The roles are set based primarily on membership in different LDAP groups, but I also need to see whether the user has a status of "approved" in the database. The LDAP portion of this is working, but I can't figure out how to allow the User Provider to call an EntityRepository method for an Entity that I have set up as a service. Basically, from inside my User Provider, I want to be able to use the Person entity repository like so:

$status = $personRepository->findStatusByUsername();

I assume I need to use Dependency Injection to make the EntityRepository available to the User Provider, but I can't seem to figure out how to do that. The problem seems to come down to the fact that the User Provider is not instantiated as an object, so I can't use $this. My latest attempt, shown in the code below, uses the property type dependency injection method, but Symfony still thinks that the constant $personRepository is undefined when I try to use it in the is_approved() method.

Error: Undefined class constant 'personRepository' in C:\xampp55\htdocs\symtran2\src\Ginsberg\TransportationBundle\Security\User\UserProvider.php line 220

As background, there is a distinction in the application between the logged-in user and a Person entity. The most common case is that the logged in user is an administrator managing Persons, although a Person can log in and manage their own information too (e.g., make reservations for themselves in the system.) The logged-in user's identity is provided by Cosign, which makes the user's username available in $_SERVER['REMOTE_USER']. As a result, you can always tell who is logged in by checking that value. The upshot of this is that there is no need for a "User" table to track usernames. There are, however, two user-information-related classes:

I have tried making the PersonRepository into a service and then making that service available to the User Provider service like so:

parameters:
ginsberg_transportation.user.class: Ginsberg\TransportationBundle\Services\User
user_provider.class: Ginsberg\TransportationBundle\Security\User\UserProvider

services:
  ginsberg_user:
    class: "%ginsberg_transportation.user.class%"
 user_provider:
    class: "%user_provider.class%"
    properties:
      personRepository: "@ginsberg_person.person_repository"
  ginsberg_transportation.form.type.person:
    class: Ginsberg\TransportationBundle\Form\Type\PersonType
    tags:
      - { name: form.type, alias: person }
  ginsberg_person.person_repository:
    class: Doctrine\ORM\EntityRepository
    factory_service: doctrine.orm.default_entity_manager
    factory_method: getRepository
    arguments:
      - Ginsberg\TransportationBundle\Entity\Person

The UserProvider class is long, but the relevant parts are:

namespace Ginsberg\TransportationBundle\Security\User;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Ginsberg\TransportationBundle\Entity\Person;
use Doctrine\ORM\EntityRepository;


class UserProvider implements UserProviderInterface
{
  public $personRepository;

   protected static $_host = 'ldap.itd.umich.edu';
    // The umbrella group that lists the subgroups of eligible drivers
    protected static $_eligible_group = 'ginsberg transpo eligible';
    protected static $_admin_group = 'ginsberg transportation admins';
    protected static $_superuser_group = 'ginsberg transportation superusers';
    protected static $_pts_group = 'ginsberg pts staff';
    public static $_pts_group_email = '[email protected]';

    public function loadUserByUsername($uniqname)
    {
      $password = "admin";
      $salt = "";
      $roles = array();

      if (self::is_authenticated()) {
        if (self::is_superuser() && self::is_approved()) {
          $roles[] = 'ROLE_SUPER_ADMIN';
        } elseif (self::is_admin() && self::is_approved()) {
          $roles[] = 'ROLE_ADMIN';
        } elseif (self::is_eligible() && self::is_approved()) {
          $roles[] = 'ROLE_USER';
        }

        return new User($uniqname, $password, $salt, $roles);
      }

      throw new UsernameNotFoundException(
      sprintf('Username "%s" does not exist.', $uniqname));
    }

    public function refreshUser(UserInterface $user) {
      if (!$user instanceof User) {
        throw new UnsupportedUserException(
          sprintf('Instances of "%s" are not supported.', get_class($user))
        );
      }

      return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class) {
      return $class === 'Ginsberg\TransportationBundle\Security\User\User.php';
    }

    /**
     * Gets uniqname based on value of $_SERVER['REMOTE_USER'] or supply hard-coded value for testing
     *
     * @return string User's uniqname or false if not found
     */
    public static function get_uniqname()
    {
      // If we are in a cosign environment, return the user uniqname from
      // REMOTE_USER
      if (isset( $_SERVER['REMOTE_USER'] ) && !empty( $_SERVER['REMOTE_USER'] )) {
        return $_SERVER['REMOTE_USER'];
      }

      // for local debug:
      if(!isset( $_SERVER['REMOTE_USER'] ) &&
          $_SERVER[ 'SERVER_NAME' ] === 'localhost') {
        return 'ericaack';
          }

    return false;
    }


    /**
     * Check whether user is logged in through Cosign.
     *
     * @return boolean Whether or not the user is authenticated
     */
    public static function is_authenticated()
    {
      if (self::get_uniqname() != False) {
        return True;
      }
      return False;
    }

    /**
     * Checks whether or not user is approved in Ginsberg transpo database
     *
     * @return boolean Whether user is approved
     */
    public static function is_approved()
    {
      $uniqname = self::get_uniqname();
      $personRep = self::personRepository;
      $status = $personRep->findStatusByUniqname($uniqname);

      return($status == 'approved') ? TRUE : FALSE;

    }

Any help would be greatly appreciated.

Upvotes: 2

Views: 954

Answers (1)

Amy Lashley
Amy Lashley

Reputation: 426

I was able to accomplish this (Symfony 3) by using an injection in the UserProvider class. I set up a class variable and setter method in the class. Additionally, I had to set up the service in my services.yml file. I'll show you both of those here.

UserProvider Class

use Doctrine\ORM\EntityRepository;
//make sure to include all other relevant uses!!

class WebserviceUserProvider implements UserProviderInterface{
private $er; //**HERE IS WHERE WE STORE THE EntityRepository**

/**
 * {@inheritDoc}
 * @see \Symfony\Component\Security\Core\User\UserProviderInterface::loadUserByUsername()
 */
public function loadUserByUsername($username)
{
    if ($username != '') {
        //WE CAN USE THE EntityRepository here!
        $user = $this->er->loadUserByUsername($username);
        if (!empty($user)){
            //Call the UserService or whatever you need to do
        }           
    }

    throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
            );
}

    /**
     * Injection method to allow us to use the EntityRepository for User
     * here. Otherwise we don't have access to it.
     * @param EntityRepository $em
    */
    public function setEr(EntityRepository $er){
        $this->er = $er;
    }

    //Include any other methods here you need for your UserProvider Class
    //I've left them out for brevity
}

services.yml You must create the repository service and also link it to the UserProvider where you need to use it!

services:
    my_repository:
        class: Doctrine\ORM\EntityRepository
        factory: ['@doctrine.orm.default_entity_manager', getRepository]
        arguments:
            - AcmeBundle\Entity\User
    app.webservice_user_provider:
        class: AcmeBundle\Security\User\WebserviceUserProvider
        calls:
             - [setEr, ['@my_repository']]

Upvotes: 1

Related Questions