I have this Behat setup:
default:
extensions:
Behat\Symfony2Extension: ~
Behat\MinkExtension:
sessions:
default:
symfony2: ~
And this scenarion:
Scenario: Event list for authenticated user
Given I am authenticated
Then I should see pagination control
And I should be able to change list page
I check if the user is authenticated and if so show him pagination control in Twig:
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
...
Related Behat context:
/**
* #Given I am authenticated
*/
public function iAmAuthenticated()
{
$user = new User('test', null, ['ROLE_USER']);
$token = new UsernamePasswordToken($user, null, 'test', $user->getRoles());
$this->getTokenStorage()->setToken($token);
}
/**
* #Then I should see pagination control
*/
public function iShouldSeePaginationControl()
{
$this->assertSession()->elementExists('css', 'ul.pagination');
}
I get true for
$this->kernel
->geContainer()
->get('security.authorization_checker')
->isGranted('IS_AUTHENTICATED_FULLY')
in my iShouldSeePaginationControl() but it is false in rendered content.
What am I missing?
My guess is that you're using a different instance of the container in your behat step and in your template.
AFAIR, the symfony2 driver uses BrowserKit under the hood to navigate through your website. The container which will be used in your web page will then be instanciated by the PHP Engine of your Web server (and not by Behat). If so, it is absolutely impossible to operate modifications in the container at runtime in a step and expect that the web server will be aware of them.
Easy solution would be to actually log in in the behat step (through the web interface) instead of setting the token manually.
Another harder way, if you absolutely want to login programatically, would be to serialize the created token on HDD and register some kind of logic (a kernel.request listener for example) that will check if this file is available and inject the unserialized token in the security context. If you do so, MAKE SURE that you enable this logic in TEST environment only, as it potentially is a security breach.
The problem is you have running 2 instances of Symfony:
One core for Behat, that was initialized.
Second, initialized by apache/nginx that was triggered by Mink connection to the server.
Solution
For that, we had a solution in another project (with Zend).
We created service, that created an additional configuration to authorization:
if a file exists and the project was in DEV mode, then it was loaded in the initialization step.
Then in hook/step we could call service that generates a file like that and after scenario, delete it. This way, you could have any logged user in your project.
Another way is to call steps that will log you into your project via a standard form.
Related
I need to set up several parameters after user authentication.
We have a db with with oauth2 clients.
When one of them got access token and is trying to get access to protected API I need to identify the client (which is easy since access_token is bound to particular client) and define several application parameters (actually I need to load a specific file parameters.yml depends on the client).
My questions is:
How can I hook to event when user is authenticated?
How can I load a particular parameters.yml and make it relevant after user authenticate ?
Thank you!
When user is successfully authenticated, you can write a listener which listens to SecurityAuthenticationEvents::AUTHENTICATION_SUCCESS.
The public method of the listener should look like this:
public function onAuthenticationSuccess(AuthenticationEvent $event)
{
/**
* #var User $user
*/
$user = $event->getAuthenticationToken()->getUser();
// ...
return;
}
I believe you can use YamlFileLoader for that. Of course you can create a service class to read the contents from yaml files and provide them to different services in your app. I would not try to mix them with regular parameter / config files.
I need to find a user object in symfony2 based on the username or emailadres. This is not for loggin in, but for other actions on a user.
I can simply request the (Doctrine2) repository and call the method loadByUsername from the UserProviderInterface that is on my repository-class.
But the code that needs to do this will be used in multiple projects and I need it to be a bit more generic. The user class/table might be different or the users might come from a completely different type of provider.
Is there a way to find a user by username just like Symfony2 itself uses when logging in?
This way it will work no matter how the user providers are configured in security.yml.
Is there some service in Symfony2 I can use for this?
Is there a "user provider service" where I can call a method something like "loadUserByUsername" that will try each configured provider?
After some poking into the SecurityBundle of Symfony itself, I figured out the following:
Given this is in your security.yml:
providers:
AdministrationUser:
entity:
class: AdministrationBundle\Entity\User
Symfony will create a service with the following name:
security.user.provider.concrete.administrationuser
This service uses the UserProviderInterface and when you fetch this service you can simply call the method loadUserByName and find your user.
So all you need to know is the name of the provider you configured yourself and you can determine the service-name and fetch it.
I'm in a more generic situation, so I added an alias to that service in the Extension-class of my bundle:
// alias the user_provider mentioned
$container->setAlias('my_security_bundle.user.provider', new Alias('security.user.provider.concrete.' . strtolower($config['user']['provider'])));
Where $config['user']['provider'] comes from config.yml (and needs to be configured in your Configuration class, but that is a different story.
Now I can simply use that new alias and I will get the correct service to find my user in a Controller like so:
/** #var UserProviderInterface $userProvider */
$userProvider = $this->get('my_security_bundle.user.provider');
$user = $userProvider->loadUserByUsername('someone#somewhere.tld');
I'm working on creating an authentication provider for Symfony 2 that allows users to authenticate with the single sign on protocol called CAS.
My Authentication Listener extends from AbstractAuthenticationListener. One of the config params is check_path, which is the path/route that triggers the authentication listener to authenticate the request.
I need check_path when I construct the URL to the CAS server (so CAS server knows where to return the user to), which is easy, since my custom Entry Point class is passed the configuration array when it's constructed in my security Factory.
The hard part is that I also need check_path outside of the listener, like during authentication inside my Authentication Provider class. I need it because when CAS server sends the user back to the app, it passes a "ticket" parameter that must be validated. To validate it, I send a curl request to CAS server that must contain the ticket as well as the original check_path that was used.
As a wrote this, I realized that I could get the current URL of the page request when I'm inside the Authentication Provider (since it's check_path that triggers it anyway), but that seems off, and I'd rather get the config value directly to re-construct the service URL. It also doesn't help me when I want to use check_path elsewhere, like when constructing a logout URL to the CAS server which also required the check_path.
EDIT: The createAuthProvider method of AbstractFactory is passed both the config and the container, but I cannot modify any of my services in here because they are not yet part of the container. Perhaps if I had a way to add a compiler pass after my services are loaded and somehow having access to the listener config?
Can you pass check_path as parameter to your listener?
If it defined in your config or parameters file you can pass it to your listener like this:
your_authentication_listener:
class: YourBundle\Listener\AuthenticationListener
arguments: ['%check_path%']
tags:
...
(If I understood you correct.)
You can make %check_path%(or a namespaced version of it) a 'normal' parameter:
Inside of DependencyInjection, there are (by default) two classes responsible for defining and loading your bundle's configuration. In there you can also inject your configuration into your service container.
DependencyInjection\Configuration is where you define which configurations are available in your bundle, what type they should be etc.
DependencyInjection\YourBundleNameExtension is where you can load your configuration and also add them to the service container.
If you have not done anything in there yet, your Extension's load()-method should look something like this:
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yml');
}
$config holds your bundle's configuration in the form of an array, so if we imagine your YAML config file looks like this:
your_bundle_name:
check_path: foo
Your $config will look like that:
array( 'check_path' => 'foo' )
So now, all you have to do is add this configuration to the container. Inside your load()-method simply add something like:
$container->setParameter(
'my_bundle_name.check_path',
$config['check_path']
);
Inside your services.yml you can now use %my_bundle_name.check_path% like every other parameter:
my_bundle_name.security.authentication.provider:
class: MyBundleName\Security\Core\Authentication\Provider\MyAuthenticationProvider
arguments: ['%my_bundle_name.check_path%']
For more details, have a look at Symfony's documentation [1,
2]
I'd like to perform a redirect before any listener is invoked.
Specifically before the oauthListener of hwi kicks in.
The reason why i want to do that:
I am implementing oauth on my symfony plateform/app
My app manages multiple 'blogs' (wordpress multisite style)
I want oauth (facebook, google...) to use a single app and not having to setup one app every time a new 'blog' is created
The problems that arose:
the redirect url must be unique (even if for google you can set multiple, i don't want to)
my blogs sometimes have aliases => are not always browsed throught the same domain
screws up with my sessions as they can't be shared across multiple domains (sub yes, domains no)
Solution i put in place so far:
in my oauth request i pass a generic domain for the redirect_uri
I'm using the state query parameter to store the domain i originated from
What i want:
i want to have a listener kernel or other that comes before everything else to check if i have this state query param set
I want it to trigger an immediate redirect of the current request to the originated domain i stored in the "state" param.
Am i doing it totally wrong ?
If i want to go on, do you know how i should declare my listener so that it comes first AND has the ability to trigger an immediate redirect ?
The kernel.request event is the first event that is being dispatched.
Have a look at the list of KernelEvents.
To have your specific listener executed before the other kernel.request listeners you can add the priority option (range: -255 to 255,highest executed first) to your listener's service configuration.
example:
services:
kernel.listener.your_listener_name:
class: Acme\DemoBundle\EventListener\AcmeRequestListener
tags:
- name: kernel.event_listener
event: kernel.request
method: onKernelRequest
priority: 255
Now all that's left for you is to perform the redirect under certain conditions inside the listener's onKernelRequest() method.
I'm sure you'll figure out how to do that. A complete code example would be out of the scope of this question.
More instructions on how to write a kernel event listener can be found in the documentation chapter:
"How to create an Event Listener"
Indeed this was it.
I tried it a few times and thought it did not work but that probably was a problem of cache clearing.
So then, for those who'd like to know :
I process my data in order to create a RedirectResponse (Symfony\Component\HttpFoundation\RedirectResponse)
and within onKernelRequest i do
public function onKernelRequest(GetResponseEvent $event){
/**
... do stuff here
*/
$event->setResponse($myResponse);
}
Is it possible in Symfony2 to configure a service by injecting data from another service? For example, by calling a getter on another service?
In my specific case I am creating a (reusable) service that can handle translatable entity fields. For this I need a list of available locales in the application. I have looked at some other bundles that also work with locales, but they always use a static array from the configuration. For example:
a2lix_translation_form:
locales: [en, fr, nl]
This configuration usually ends up mapping to the service in the form of a constructor parameter or setter via the bundle configuration. For example:
class SomeService {
function __construct(array $locales) { ... }
// or
function setLocales(array $locales) { ... }
}
But in my case the list of available locales is not always static and often comes from the database. I have created a Locale service in my application with a method getLocales that returns an array. But how do I get that array into my service that needs it?
The service I am creating that needs a list of locales is split off into a separate reusable bundle. I don't want to inject the Locale service directly because that service is specific to the application, and not the bundle I am creating. I want users of my bundle to be able to provide a static list of locales, or point towards a service that has all the locales.
I would solve this problem using semantic configuration and config defintions. It works pretty similar to how FOSUserBundle asks for a driver and uses different settings depending on your choice (orm, mongodb, propel).
You could add something like this to your config.yml:
a2lix_locale:
provider: default # database
# ... additional settings which are optional,
# but required by provider, e.g. database settings
Your bundle's Configuration.php would verify that a valid provider was selected and that additional settings are set according to what each provider requires. Again, FOSUserBundle provides a great example for how to do this.
Additionally in your bundle's MyBundleExtension.php in /DependencyInjection you can access the service container and pass for instance the parameter locale to your default service in order for it to use the application's default locale provided in parameters.yml.