I'm using fr3d/ldap-bundle. It logs me in and imports users from AD if they're not in db. That's fine.
Despite AD users I also have local users, which are in my db. There is special column authType which says how user should be authenticated - via LDAP or natively ( FOS ). I've created my own user provider:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
PROBLEM: Chain provider isn't an option - it allows user to login with his LDAP password AND with his local password! That's a big security issue.
Is there a way to login user via different authentication providers, depending on the db field?
EDIT:
My security.yml:
providers:
fos_userbundle:
id: fos_user.user_provider.username
appbundle_user_provider:
id: appbundle.user_provider
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin.*
context: user
fr3d_ldap: ~
form_login:
provider: appbundle_user_provider
csrf_provider: security.csrf.token_manager
always_use_default_target_path: true
default_target_path: admin_main
login_path: /admin/login
check_path: /admin/login_check
logout:
path: /admin/logout
target: /admin/login
anonymous: true
Here is security.yml. This line fr3d_ldap: ~ enables the ldap bundle, which authorize ldap users and saves them into my db. Without it I cannot authorize them, probably I would have to write custom AuthenticationProvider.
I am not very familiar with ldap but I would suggest try doing a completely manual login
$token = new UsernamePasswordToken($user, null, "firewallname", $user->getRoles());
$securityContext = $this->container->get('security.context');
$securityContext->setToken($token);
Then you can manually do the checks yourself, and depending on the result of the check decide how you want to verify the user before authenticating. For example, run a query by username and password before executing this login code or whatever, depending on the db field you want.
Your approach seems fine but you should check logic of your methods.
First of all this one:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
You pass $username to this method as an argument, but then use $user object, which seems to be undefined in current context.
Secondly:
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
So as chooseProviderForUsername method actually does not return any value you are not able to chain it this way.
I hope refactoring these issues should make your provider work properly.
Ok, so very brief answer, but I think at the moment Symfony is searching for the user amongst any old User Provider rather than the one you want it to for that particular user (which explains the whole logging in with two passwords thing). A solution should be to make AppBundleUserProvider implement UserProviderInterface, remove the other User Providers from security.yml and then to ensure that the first thing AppBundleUserProvider does it to find out which User Provider is required for that user then mimic it for every method in the UserProviderInterface. You could set $this->realUP based on Username, then set every method to just return $this->realUP->someMethod().
The cleanest way I can think of is to create your own ChainProvider class that only allows login with one provider and use the Dependency Injection Container to use yours.
You just need to override the security.user.provider.chain.clas parameter definition in your bundle's config file.
Related
I want to get user inside a profile service, but using tokenStorage and SecurityContext, the user will be everytime null.
that's my services.yml file:
project.service.profiler:
class: Project\Service\Profiler
arguments:
- "#security.helper"
- "#=service('doctrine').getRepository('bundle:ProfileKey')"
- "#=service('doctrine').getRepository('bundle:ProfileKeyUsers')"
- "#=service('doctrine').getRepository('bundle:ProfileKeyRoles')"
- "#logger"
- "#security.token_storage"
- "#security.authorization_checker"
- "#fos_oauth_server.access_token_manager.default"
and that's my class Profiler
class Profiler
{
public function __construct(
Security $security,
ProfileKeyRepositoryInterface $profileKeyRepository,
ProfileKeyUserRepositoryInterface $profileKeyUserRepository,
ProfileKeyRoleRepositoryInterface $profileKeyRoleRepository,
$logger,
$tokenStorage,
$authChecker,
TokenManagerInterface $tokenManager
){
if ($tokenStorage && $tokenStorage->getToken() && $tokenStorage->getToken()->getUser()) {
$this->user = $tokenStorage->getToken()->getUser();
}
}
}
The problem is that tokenStorage->getToken is always null (I'm sure, I'm logged in!).
So, this profiler was called from a controller, where the user is present, then I suspect that when the profiler was called during the symfony loading flow, the user is not created yet.
finally, if I set this line of code:
$security->isGranted('IS_AUTHETICATED_FULLY'); --> thrown an Exception
or getToken method:
$security->getToken() --> return null
I obtain everytime this error:
Why this behaviour?
In previous symfony version (I mean 3.3) this problem never occurred.
Thanks a lot to anyone who helps me
Update 08/04/2019
Following the symfony3.2 docs (https://symfony.com/blog/new-in-symfony-3-2-firewall-config-class-and-profiler), this post says to check if the request is under firewall, otherwise the user token should be null.
But, in my case, I checked with debug toolbar that all it's ok.
Finally, I absolutely have no idea why the user token is null under my service
Here my security firewall section:
security:
restricted_area:
anonymous: ~
access_denied_url: /unauthorized
access_denied_handler: app.security.access_denied_handler
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /
Finally I've found the answer myself!!
So, the problem was in my class code inside the service class: I was trying to obtain the user directly in the constructor (in the symfony flow, services are loaded before token management), and here the TokenStorage->getToken was always null.
So, when I need to obtain the user inside the service procedures, the tokenStorage->getToken() returns the correct value.
Hope that this answer can help someone with my same (old) problem.
We have a site where people can login using a hash. We don't have distinct users. Everyone with a valid hash can login and it's not important to distinguish who is logged in.
I already created a form to get the hash in the controller. Here's the simplified code:
public function index(Request $request) {
if ($request->isMethod('post')) {
$token = $request->request->get('token');
$hash = $request->request->get('hash');
if ($this->isCsrfTokenValid('login', $token) && $this->isHashValid($hash)) {
// redirect
}
// fail
}
return $this->render('login.html.twig', []);
}
Now in $this->isHashValid() I can already say whether a hash is valid. But I'm not sure, how to authenticate the visitor manually:
isHashValid($hash) {
// pseudo-check for the question
if (in_array($hash, $hashes) {
// How to authenticate the user?
return true;
}
return false;
}
I also updated the security.yaml to redirect unauthenticated visitors to the startpage, which works already:
security:
providers:
in_memory: { memory: ~ }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
form_login:
login_path: '/'
access_control:
- { path: ^/secured, allow_if: "is_authenticated()" }
So my question is, how can I authenticate and later log out a visitor programmatically in Symfoy 4?
Do I still need to create a User-class, even though we don't have "real users" in a classical sense?
You will have to create a User implementing the UserInterface, but it can be a barebones class that does not require you to set any values. Just make sure that getRoles() returns at least ["ROLE_USER"] and everything else should be ok to just return dummy data or null values.
The authentification can be solved multiple ways.
GuardAuthenticator seems like a good solution: https://symfony.com/doc/current/security/guard_authentication.html#step-2-create-the-authenticator-class
FormLoginAuthenticator is pretty similar, but the some methods are automatically dealt with based on using a login form, so it's a bit easier to implement: https://symfony.com/doc/current/security/form_login_setup.html
In both cases you could basically do it like this: getCredentials you extract the data from the request, in getUser you return your dummy user object and in checkCredentials you call your isHashValid method the rest should be self explanatory.
In this case, after a successful login, I need to update the user's login time into the underlying table.
The User entity currently implements UserInterface and is doing fine. Just want to add some extra code to log the login date time of the user.
Searched the site and seemed to me to use an EventListener etc is a bit heavy. Other lighter alternatives?
You can implement a Success Hander.
Write a class that implement the AuthenticationSuccessHandlerInterface:
Define it as a service (you can inject other services like doctrine
or the security context, in your case the Session)
Add the handler service in the security config like:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
success_handler: some.service.id
logout:
path: logout
target: /
Check this for a complete example and the reference doc for all the symfony2 security configuration options (you can configure the failure_handler also).
In my working solutions I implements the method onSecurityInteractiveLogin in my listener as:
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$user->setLastLogin(new \Datetime());
// Entity manager injected by container in the service definition
$this->em->persist($user);
$this->em->flush();
}
Hope this help
I have the following scenario: I have a secured area of my domain under the pattern "/register", for which I have associated a fixed user called "registrant", with the unique role USER_REGISTRANT. The relevant security.yml sections are:
providers:
in_memory:
memory:
users:
registrant: { password: registrant, roles: 'REGISTERING_USER' }
firewalls:
register:
pattern: ^/register/.*
anonymous: false
form_login:
login_path: /register/initiate_registration
check_path: /register/start_registration
My goal is the following: whenever the user tries to enter the "/register" security context, she should be automatically authenticated as the user "registrant", without any form interaction or other user-side authentication steps.
I want to achieve this using the standard form-login mechanisms in Symfony2, i.e. when the user is sent to the login_path, the system should simply generate the necessary token/form data and pass it to check_path, just as would be done if the user had filled in a form and submitted it.
The general outline of the logic should go something like this:
/**
* #Route("/register/initiate_registration", name="initiate_registration")
*/
public function startAction() {
// TODO: Generate form data etc here
return $this->redirect($this->generateUrl('start_registration'));
}
What steps should be taken in the login_path controller in order to get the functionality desired above?
Is this docs can be usefull for you security?
I'm trying to find out if a user is inside a secure firewall.
security.yml:
firewalls:
non_secure_area:
pattern: ^/
anonymous: true
secure_area:
pattern: ^/admin
form_login:
#etc.
logout:
#etc.
So I need to know if the user is inside the 'secure_area' secure part of the site.
I have used this, but of course it only tells me if somebody is 'logged in' AND on a HTTPS page. There must be a better way:
if( $request->isSecure() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') ) {
}
You can get security token and access provider key on it.
$token = $securityContext->getToken();
$providerKey = $token->getProviderKey(); // secured_area
Dont forget to check that token exist and its not an instance of AnonymousToken
If you are into something that is ContainerAware, you may get the Request, and then the URI [see docs]:
$request = $this->container->get('request');
$uri = $request->getUri();
Then you can check such string against /admin as you wish.