I have a user with ROLE_ADMIN and I want to connect him as "ROLE_USER" when calling http://localhost/login but connect him as "ROLE_ADMIN" when calling http://localhost/login?role=admin.
Is it possible to do it in Symfony ?
You can just check if the "role" GET parameter is set and defined as "admin" and then write your code.
If there is a role inheritance and that your admin has ROLE_USER and ROLE_ADMIN, just define custom actions if you detect that it's an admin:
if ($this->isGranted('ROLE_ADMIN')) {
//adminCode
} else {
//userCode
}
Related
I am working on a symfony project and I have a question in the authentication of users that I would like you to solve, since I am new to this.
My project is about a school, where there are teacher type users that have the username and password attributes in the database. The user name consists of the letter t followed by the personal identification number, for example t48945110. In addition, teachers have a Boolean attribute to indicate which of them is the school principal (there is only one).
The security settings in my project are as follows:
Secutiry.yml
security:
firewalls:
intranet:
pattern: ^/
anonymous: ~
provider: teachers
form_login:
login_path: /login
check_path: /login_check
# use_referer: true
default_target_path: /teacher
logout: ~
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: admin/, roles: ROLE_ADMIN }
- { path: /teacher, roles: ROLE_TEACHER }
providers:
teachers:
entity: { class: School\BackendBundle\Entity\Teacher, property: username}
encoders:
School\BackendBundle\Entity\Teacher: { algorithm: sha512 }
With the above, the teacher can accerder to his private part through the login form, but the problem I have when I want to access with the director (teacher with the attribute (director=1) in the database). I would like this teacher to be able to access with another username, but not save it in the database, just changing the main letter, and then in the authentication look for the normal username of the teacher and check if the director to redirect it to / admin instead of /teacher. For example, the head of the school who can access his personal area as a teacher by the user name t48945110 and also can access the administration area of the school with the user name d48945110.
This idea is not to create two different login forms, but to access all the users of the application (students, teachers, director ...).
I do not know if it could be done through Events listeners or otherwise. I appreciate your help for help.
I think you might be overcomplicating things. I'm pretty sure your director does not want to login with different usernames to enter the separate sections. Without knowing more details, what I would recommend is changing the getRoles() in your School\BackendBundle\Entity\Teacher to something like this:
public function getRoles()
{
$roles = array('ROLE_TEACHER');
if ($this->director == 1) {
$roles[] = 'ROLE_DIRECTOR';
$roles[] = 'ROLE_ADMIN';
}
return $roles;
}
This is probably the easiest way to have the user be both a teacher having access to their secured area as well as director having access to the admin area without having to switch accounts.
If for some reason this does not work you could have a look at Security Voters. This could look something like this:
class TeacherIsDirectorVoter extends Voter
{
protected function supports($attribute, $subject)
{
// This voter will always be used
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
// This voter will allow access if the currently logged in user is teacher and is a director
return ($user instanceof Teacher && $user->isDirector());
}
}
Again this will grant access for a teacher who is a director without requiring them to log in as a different user. Be careful with this voter as it will always grant access to all sections of your site to the teacher having director=1 with the default voter strategy. There are ways to change this or you could add checks based on the provided attributes and the subject being voted on.
I think for your use case the first option (updating the roles) is probably the easiest and will work well for you.
If you want students, teachers and director to be redirected to different pages after login you could do a little trick. In your SecurityController (or wherever you have your loginAction) create a new targetAction() that is used as target after successful login. In your security.yml assign the form_login's default_target_path to that route, instead of /teacher. That action should be accessible by all 3 user groups. Now just redirect, based on whatever roles your user has:
public function targetAAction()
{
$user = $this->getUser();
if (in_array('ROLE_ADMIN', $user->getRoles())) {
return $this->redirectToRoute('intranet_admin_page');
}
if (in_array('ROLE_TEACHER', $user->getRoles())) {
return $this->redirectToRoute('intranet_teacher_page');
}
return $this->redirectToRoute('generic_page_for_users');
}
I want to set role for user. I ąm using code below:
$user = this->getDoctrine()->getRepository('BlogBundle:User')->findOneById('11');
$em = $this->getDoctrine()->getManager();
$t=array('ROLE_ADMIN');
$user->setRoles($t);
$em->persist($user);
$em->flush();
return new Response('okk');
When I set 'ROLE_ADMIN' everything is ok, but when I set 'ROLE_USER' database cell is empty. Why?
Thank you for help in advance.
The roles column in your users table, should have
a:0:{}
for users with just the role ROLE_USER
Programmatic way of checking .
check if a user has a particular role ( inclusing ROLE_USER )
if ($this->get('security.context')->isGranted('ROLE_USER')) {
// the user has the ROLE_USER role
}
there's also
$user->getRoles()
One more way to double check if the use really has the ROLE_USER
php app/console fos:user:promote
When prompted, enter the username followed by ROLE_USER
it should state that
User "example#example.com" did already have "ROLE_USER" role.
In FOS User Bundle, all users have the ROLE_USER role. It would be redundant to add/save this role.
If you're looking for it in FOSUserBundle's code base, the role is aliased as ROLE_DEFAULT in the UserInterface class.
I have a role hierarchy defined in my security.yml config file :
role hierarchy:
ROLE_USER: [ROLE_USER]
ROLE_OFFICE: [ROLE_OFFICE]
ROLE_TEST: [ROLE_OFFICE, ROLE_USER]
I want to use the security annotations in my controller :
/**
* #Route("/office", name="office")
* #Security("has_role('ROLE_OFFICE')")
*/
How can i use the role hierarchy with annotation. With my example, a user with ROLE_TEST will not be allowed to access my office route.
Thank you.
When you defining new role, you're defining from which existing role(s) will the role inherit. Your definition here is wrong.
You don't have to define ROLE_USER, it will be defined once you extend it. (thanks to #Yonel)
As I mentioned above, new role should extend existing one:
role_hierarchy:
ROLE_OFFICE: ROLE_USER
...
ROLE_OFFICE will also have the ROLE_USER
Your ROLE_TEST now has also ROLE_OFFICE, this means that ROLE_TEST is allowed to access route /office. To fix this, you have to remove ROLE_OFFICE from your definition.
This should be working definition:
role_hierarchy:
ROLE_OFFICE: ROLE_USER
ROLE_TEST: ROLE_USER
Documentation: http://symfony.com/doc/current/security.html#hierarchical-roles
If you change a little bit your annotation by :
#Security("is_granted('ROLE_OFFICE')")
Does the ROLE_TEST access the ressource?
If you want to exclude a route to a specific role you can negate the condition. As Example:
/**
* #Route("/office", name="office")
* #Security("not has_role('ROLE_TEST')")
*/
Hope this help
I'm trying to implement very basic authentication in Symfony2. Here are main parts of the code I really don't see any problem
EDIT
complete security.yml
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
firewalls:
login:
pattern: ^/login
anonymous: ~
secured_area:
pattern: ^/
stateless: true
form_login:
login_path: /login
check_path: /login_check
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
This works fine, anonymous user is always redirected to loginAction controller.
EDIT
Here is the complete code
<?php
namespace AcmeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class SecurityController extends Controller {
public function loginAction() {
$providerKey = 'secured_area';
$token = new UsernamePasswordToken('test', 'test', $providerKey, array('ROLE_USER'));
$this->container->get('security.context')->setToken($token);
return $this->redirect($this->generateUrl('fronthomepage'));
}
}
I don't see any problem, anonymous user is redirected to loginAction, there is created authenticated user, saved to token and than redirected to secured area as an authenticated user. Unfortunately my code ends with redirect loop which looks like security firewall doesn't accept user as authenticated. Do you see any problem?
Well, your controller job is to render just form but not to populate security context. Symfony2 security firewall will do that for you automatically. You don't need to handle it unless you want to build you own custom authentication.
In other words, your job is to display the login form and any login
errors that may have occurred, but the security system itself takes
care of checking the submitted username and password and
authenticating the user.
Please read this document for clear picture.
If you want to do some custom stuff when a user logs in, in Symfony2 you have to add an event listener that will fire after the user successfully logged in. The event that is fired is security.interactive_login and to hook to it you have to specify this in services.yml file form your bundle Resources/config directory:
Pretty sure you need an actual user object before setting an authenticated user. I did something like this:
class BaseController
protected function setUser($userName)
{
if (is_object($userName)) $user = $userName;
else
{
$userProvider = $this->get('zayso_core.user.provider');
// Need try/catch here
$user = $userProvider->loadUserByUsername($userName);
}
$providerKey = 'secured_area';
$providerKey = $this->container->getParameter('zayso_core.provider.key'); // secured_area
$token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
$this->get('security.context')->setToken($token);
return $user;
}
However doing something like this bypasses much of the security system and is not recommended. I also wanted to use a 3rd party authentication system (Janrain). I looked at the authentication system and initially could not make heads or tails out of it. This was before the cookbook entry existed.
I know it seems overkill but once you work through things then it starts to make more sense. And you get access to a bunch of nifty security functions. It took me quite some time to start to understand the authentication system but it was worth it in the end.
Hints:
1. Work through the cook book backward. I had a real hard time understanding what was going on but I started with adding a new firewall to security.yml and then adding the alias for my security factory. I then sort of traced through what the factory was being asked to do. From there I got the listener to fire up and again traced through the calls. Finally the authentication manager comes into play. Again, time consuming, but worth it in the end. Learned a lot.
One thing that drove me crazy is that classes are scattered all over the place. And the naming leaves something to be desired. Very hard to get an overview. I ended up making my own authentication bundle then putting everything under security.
If you want another example of a working bundle then take a look at: https://github.com/cerad/cerad/tree/master/src/Cerad/Bundle/JanrainBundle
I see from the official Symfony2 doc on Security that new roles can be defined besides the "classical" ones (i.e. ROLE_USER, ROLE_ADMIN, etc.).
How can I define new roles and register them to my Symfony2 application in order to create roles hierarchy in the security.yml?
Sorry to have bothered all of you! I think that the answer is simple. In fact, it seems that is sufficient to start to use a new role by starting the name with ROLE_.
E.g., it is possible to say ROLE_NEWS_AUTHOR to let only people with that role to be capable to insert a news in the website.
Thanks.
Sure you can simply add any roles starting with ROLE_SOMEROLE.In security.yml file there are two main part to 1.limit the access 2. Who are the memebers can access
a. access_control: Which limit the pattern and specify a role who can access.
b. role_hierarchy: here the hierarchical structure of role, for the below example an Admin user(ROLE_ADMIN) have roles ROLE_USER,ROLE_NEWS_AUTHOR. So he can access all pages of a USER and NEWS_AUTHOR.Whatever the hierarchy you can give.
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }/login any one can access this pattern
- { path: ^/admin/, roles: ROLE_ADMIN }//block all pattern /admin/anything*
- { path: ^/news/, roles: ROLE_NEWS_AUTHOR } //block all pattern /news/anything*
role_hierarchy:
ROLE_ADMIN: [ROLE_USER,ROLE_NEWS_AUTHOR]
In your controller you can check the roles,
if(TRUE ===$this->get('security.context')->isGranted('ROLE_ADMIN') )
{
// do something related to ADMIN
}
else if(TRUE ===$this->get('security.context')->isGranted('ROLE_NEWS_AUTHOR') )
{
// do something related to News Editor
}
Hope this helps you .
HAppy coding.