Reputation: 199
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:
The User class implements UserInterface and EquatableInterface and seems to be the object that the Symfony security system uses to manage authorization. (I created this class by following the instructions in the Cookbook for creating a custom user provider: http://symfony.com/doc/current/cookbook/security/custom_provider.html.)
The Person entity mentioned above, which tracks information about the status of users in the system, such as the stage a given Person is in during the approval process.
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
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