Case-insensitive user-names in Symfony2 - symfony

By default, Symfony2 matches usernames case-sensitively. I want users to be able to enter johnsmith, JohnSmith, johnSMITH, or any variant of that, and have them all register as johnsmith. How do I do this?
I though the easiest way would be to always convert each username to lower-case before comparing them. This is easy to do on the one side (just throw a lower() into the SQL statement), but how do I do that for what the user types in in the login form? Since Symfony automatically takes care of the login_check route handling, I can't figure it out.
Better yet, is there some option I can set to enable case-insensitivity instead?

Just write correct loadUserByUsername function in UserRepository.
It must not be case sensitive:
public function loadUserByUsername($username)
{
$user = $this->createQueryBuilder('u')
->where('LOWER(u.email) = :username')
->setParameter('username', strtolower($username))
->getQuery()
->getOneOrNullResult();
if ($user){
return $user;
}
throw new UsernameNotFoundException('Unable to find user "' . $username . '"');
}

You already fixed it, but I will explain another solution for users with similar problems:
You have to implement your own Provider this way: http://symfony.com/doc/current/cookbook/security/entity_provider.html#authenticating-someone-with-a-custom-entity-provider
Use the following query instead in method loadUserByUsername:
$user = $this->findOneBy(array('username' => strtolower($username)));
This worked for me. (Also in Doctrine Mongo ODM)

Did you try to convert the input into lowercase using CSS ? There are actually ways to control data input before it is handed to the login_check controller, but if you want a quick fix :
p {
text-transform: lowercase;
}

In your setUsername you could just have the text changed to lowercase like..
public function setUsername($username)
{
$this->username = mb_strtolower($username);
return $this;
}
For reference, FOSUserBundle handles this by "canonicalizing" the username (to usernameCanonical) and email address (to canonicalEmail) in the updateUser call (see Doctrine\UserManager that calls the Canonicalizer) which it then uses for searches.

I feel like an idiot. All I had to do was add another lower() around the where clause in my SQL statement. Like this:
select lower(USERNAME) as USERNAME, PASSWORD, ROLES, ALL_CUSTOMER_ACCESS
from COMPANYNAME_USERS
where lower(USERNAME) = lower(:username)

Related

Easy admin list modification

I don't get this guys!
At this moment I still have only one entity (User). I manage my users with the FOSUserBundle.
I want to modify the fields displayed in my list. Like this right?
config.yml
easy_admin:
entities:
Users:
class: AppBundle\Entity\User
list:
fields:
- username
- email
- last_login
But I get this error when trying to do it;
An exception has been thrown during the rendering of a template
("Warning: mb_strlen() expects parameter 1 to be string, object
given") in #EasyAdmin/default/field_text.html.twig at line 4.
I've added a __toString() method in my User entity but it still doesn't work;
User.php
public function __toString()
{
return $this->getUsername();
}
I am pretty new to the whole Symfony thing, so can somebody please help me out?
This issue is fixed and will probably be available with the next stable release.
In the meantime you can fix this by manually copy paste this 5 lines into EasyAdminTwigExtention.php at line 269.
try {
$value = (string) $value;
} catch (\Exception $e) {
$value = '';
}
Look here for the same question I asked on Git. And here where the code is modified.

How to show symfony roles

I'm working with symfony 2.3. How I can show the user roles without (ROLE_). I want to change the view, but in the database are intact.
When I display the roles in the view I have this
ROLE_ADMIN
ROLE_CONSULTOR
and I want
Admin
Consultor
Role Entities just need to implement the RoleInterface. You can add your own custom fields such as the names you want.
http://api.symfony.com/2.3/Symfony/Component/Security/Core/Role/RoleInterface.html
Not clear what you want to achieve, but if there are few roles, may be 2-3, (e.g. ROLE_ADMIN, ROLE_CONSULTOR, ROLE_USER), just define a method which receives the system role, returns the human readable one. May be like this:
public function convertToHumanreadable($role)
{
$return = null;
switch ($role) {
case 'ROLE_ADMIN':
$return = 'Admin';
break;
...
}
return $return;
}
Or even like this:
public function convertToHumanreadable($role)
{
$roleParts = explode('_', $role)
return ucfirst(strtolower($roleParts[1]));
}
Or you can create your own role entity by implementing the RoleInterface as #DerickF mentioned.

Sonata Admin - how to set the menu.label attribute?

According to the Sonata source code, the last node in the breadcrumb is rendered this way:
# standard_layout.html.twig #
<li class="active"><span>{{ menu.label }}</span></li>
In my setup, when opening a given Admin subclass, the last node simply becomes a raw string according to the entity handled by the Admin:
Dashboard / Entity List / Acme\SomeBundle\Entity\Stuff:000000001d74ac0a00007ff2930a326f
How can I set the value of menu.label to get something more appropriate? I have tried, in my Admin subclass, to override the following:
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$this->configureSideMenu($menu, $action, $childAdmin);
}
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$menu->setLabel("Some nice label");
$menu->setName("Some nice name");
}
However, this does not change anything, even though I have verified that the methods above are called during runtime.
Finally found a good (and somewhat obvious) solution to this.
The Sonata Admin class uses an internal toString($object) method in order to get a label string for the entity it is handling. Thus, the key is to implement the __toString() method of the entity in question:
public function __toString() {
return "test";
}
The best way is to configure the $classnameLabel variable in the Admin Class :
class fooAdmin extends Admin
{
protected $classnameLabel = 'Custom Label';
}
But it have the same issue (weird name with entity path) doing it, even if it is working fine on all the others pages.
Apparently, the Sonata way of solving this is show here:
Quote:
While it’s very friendly of the SonataAdminBundle to notify the admin of a successful creation, the classname and some sort of hash aren’t really nice to read. This is the default string representation of an object in the SonataAdminBundle. You can change it by defining a toString() (note: no underscore prefix) method in the Admin class. This receives the object to transform to a string as the first parameter:
Source: https://sonata-project.org/bundles/admin/master/doc/getting_started/the_form_view.html#creating-a-blog-post

Symfony2 Application Architecture - how to make a function available in all controllers?

I'm building an application where users are tied to accounts (as in, multiple users all share an account), then other entities - lets call them products are tied to the accounts. The products are associated with the accounts and only users that are tied to that account can edit the products.
The difference being in my case, there are many different entities being shared in the same model.
If it was just the one (product) entity, it wouldn't be a problem to have a method in my ProductRepository like:
public function checkOwnership($id, $account)
{
$count = $this->createQueryBuilder('s')
->select('count(s.id)')
->where('s.account = :acc')
->andWhere('s.id = :id')
->setParameters(array('id' => $id, 'acc' => $account))
->getQuery()
->getSingleScalarResult();
if($count == 0)
throw $this->createNotFoundException('ye..');
return;
}
To make sure the id of the product is tied to the user's account.
The account argument is passed to this function by using:
$this->getUser();
In the controller.
What is the best way for me to make this function available to the other entities to prevent code duplication? There are (currently) only 3 other entities, so it wouldn't be the end of the world to just have it in each repository, but I'm just wondering if there were a way to create a 'common' or global repository that follows good standards? I'd sure like to know about it.
Edit:
Or have I just completely over-thought this? Can I just create a 'Common' directory in my 'MainBundle', define a namespace and add a use statement at the start of my controllers that need access to the function?
I hope I fully understand your question.
Solution one, duplication, easy: let #ParamConverter do the job (automatic response to 404 if doesn't exist)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/pets/{id}")
*/
public function showAction(Pet $pet)
{
// Current logged user
$currentUser = $this->get('security.context')->getToken()->getUser();
// Owner
$ownerUser = $pet->getUser();
if(!$currentUser->equals($ownerUser)) {
// 401, 403 o 404 depends on you
}
}
Solution two, DRY, a bit harder: use JMSAOPBundle and define an interceptor that intercepts all request to you controller and actions (show, delete, etc.). The pointcut (connected to the interceptor) will get the current user from the context and the owner from the entity. Then you do the check...

Right form events to display modified data and update modified data?

A simple task: before displaying the form, if $data->getRole() starts with "ROLE_", remove this string and display only the rest. When user submit the form, do the opposite: add "ROLE_" before the name.
What's the best place to do this? Actually i'm using PRE_SET_DATA and POST_BIND. Are these the right events to perform this operation?
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function(DataEvent $event){
if(is_null($data = $event->getData()) || !$data->getId()) return;
$data->setRole(strtoupper(preg_replace('/^ROLE_/i', '',
$data->getRole())));
});
$builder->addEventListener(FormEvents::POST_BIND,
function(DataEvent $event) {
if(is_null($data = $event->getData()) || !$data->getId()) return;
$data->setRole('ROLE_' . strtoupper($data->getRole()));
});
Well reading the role without the prefix "ROLE" is not something I would do using events. As they obsfusicate your workflow, events should be used with care! Working with symfony for some time, I used them once or twice when there was really no other way. All the other times there was a better way.
I would tend to simply add a function getShortRole and setShortRole and use shortRole within your Entity:
class MyEntity {
private $role;
public function setShortRole($role) {
$this->role = 'ROLE_' . strtoupper($role);
}
public function getShortRole() {
return strtoupper(preg_replace('/^ROLE_/i', '', $this->role));
}
}
You are saving yourself a lot of trouble working with models instead of events!
A second, more complicated way would be to use a Model which represents the form instead of the Entity and maps the form to the entity. Here is a good article about this here!
I use it myself and it works nice.

Resources