Symfony multicompany application - symfony

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.

Related

Twig is_granted fails in Behat scenario

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.

find user by username from multiple providers [symfony2]

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');

How to access a Symfony service that is used by a client request in a web test case?

I have an application that relies on third party services. In order to make sure that the application works properly, I want to mock the third party services and make sure that the application is working as expected.
This requires that I am able to configure the mock services before creating the requests. However, I am unable to do so.
Consider the following code:
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
//..
class MyTest extends WebTestCase
{
public function testSignupLink()
{
$container = static::createClient()->getContainer();
// This returns a different instance from the one used by the client request
$service = $container->get('third-party-service');
$service->setErrorState(MockService::SOME_ERROR_STATE);
// ...
// The request creates a new instance of the $service internally which doesn't have the error state that was set above
$client->request('POST', '/abc/1');
}
}
The 'abc' controller relies on a service that I can't access. When I access the service from the container, I get a different instance from the one that is used by the client request.
Is there any way to handle this?
If I correctly understood you, here is what you need:
https://github.com/PolishSymfonyCommunity/SymfonyMockerContainer

Simple API Key Authentication in Symfony2 using FOSUserBundle (and HWIOauthBundle), filling in the gaps

Edit: See below for my own solution, which is, at the time of writing, functioning but imperfect. Would love some criticism and feedback, if I get something put together that I feel is really solid then I'll make a howto blog post for other people facing the same challenge.
I've been struggling with this for days, and I'm hoping someone can let me know if I'm on the right path.
I have a system with a FOSRestBundle webservice in which I'm currently using FOSUserBundle and HWIOAuthBundle to authenticate users.
I would like to set up stateless api key authentication for the webservice.
I've read through http://symfony.com/doc/current/cookbook/security/api_key_authentication.html and this seems simple enough to implement, I've also installed UecodeApiKeyBundle which seems to be mostly just an implementation of this book page.
My question is a n00b one...what now? The book page and bundle both cover authenticating a user by API key, but don't touch on the flow of logging users in, generating API keys, allowing users to register, etc. What I would really like is simple API endpoints for login, register, and logout that my app developers can use. Something like /api/v1/login, etc.
I think I can handle registration....login is confusing me though. Based upon some additional reading, it seems to me like what I need to do for login is this:
Create a controller at api/v1/login that accepts POST requests. The
request will either look like { _username: foo, _password: bar } or
something like { facebook_access_token: foo. Alternately, the facebook login could require a different action, like /user/login/facebook, and just redirect to the HWIOAuthBundle path }.
If the request contains _username and _password parameters, then I
need to forward the request to login-check (I'm not sure about this
one. Can I just process this form myself? Or, should I manually check
the username and password against the database?)
Add a login event listener, if user authenticated successfully,
generate an api key for the user (This is only necessary if I'm not checking it myself, of course)
Return the API Key in the response of the POST request (This breaks
the post-redirect-get strategy, but otherwise I don't see any issues
with it) I think this eliminates the redirect to login-check option I
listed above.
As you can probably see I'm confused. This is my first Symfony2 project, and the book pages on Security sound simple...but seem to gloss over some of the details and it's left me quite unsure of what way to proceed.
Thanks in advance!
=============================================================
Edit:
I've installed a API Key Authentication pretty much identically to the relevant cookbook article: http://symfony.com/doc/current/cookbook/security/api_key_authentication.html
To handle user's logging in, I've created a custom controller method. I doubt that this is perfect, I would love to hear some feedback on how it can be improved, but I do believe that I'm on the right path as my flow is now working. Here's the code (Please note, still early in development...I haven't looked at Facebook login yet, only simple username/password login):
class SecurityController extends FOSRestController
{
/**
* Create a security token for the user
*/
public function tokenCreateAction()
{
$request = $this->getRequest();
$username = $request->get('username',NULL);
$password = $request->get('password',NULL);
if (!isset($username) || !isset($password)){
throw new BadRequestHttpException("You must pass username and password fields");
}
$um = $this->get('fos_user.user_manager');
$user = $um->findUserByUsernameOrEmail($username);
if (!$user instanceof \Acme\UserBundle\Entity\User) {
throw new AccessDeniedHttpException("No matching user account found");
}
$encoder_service = $this->get('security.encoder_factory');
$encoder = $encoder_service->getEncoder($user);
$encoded_pass = $encoder->encodePassword($password, $user->getSalt());
if ($encoded_pass != $user->getPassword()) {
throw new AccessDeniedHttpException("Password does not match password on record");
}
//User checks out, generate an api key
$user->generateApiKey();
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
return array("apiKey" => $user->getApiKey());
}
}
This seems to work pretty well, and user registration will be handled similarly.
Interestingly to me, the api key authentication method I implemented from the cookbook appears to ignore the access_control settings in my security.yml file, in the cookbook they outline how to only generate the token for a specific path, but I didn't like that solution, so I've implemented my own (also somewhat poor) solution to not check the path I'm using to authenticate users
api_login:
pattern: ^/api/v1/user/authenticate$
security: false
api:
pattern: ^/api/*
stateless: true
anonymous: true
simple_preauth:
authenticator: apikey_authenticator
I'm sure there's a better way to do this too, but again...not sure what it is.
You are trying to implement stateless authentication with username and login. This is pretty much what the Oauth2 authentication passsword grant does. This is pretty standard, so instead of trying to implement it yourself i'd recommend you use a Bundle for that, for example the FOSOauthServerBundle. It can use FOSUserBundle as its user provider and would be cleaner, more secured and easier to use than a home-made solution.
To register user, your can create a register action in your API (e.g., in a REST API I'd use POST - api/v1/users), and in the controller method copy and past the code from the FOSUserBundle:RegistrationController (of course adapt it for your needs).
I did that in a REST API, it worked like a charm.
I don't think you actually really need a /login endpoint.
In the symfony doc, the api client is required to pass it's key (via apiKey http parameter) to every request to the API.
I am not sure it's in the best practice, but you could do this.
"The book page and bundle both cover authenticating a user by API key, but don't touch on the flow of logging users in, generating API keys, allowing users to register"
The best is to allow your users to register via a web form (for example with the route fos_user_register). User entity could have an apikey field, pre-populated with a key generated like this sha1("secret".time()) for example, and a button in their profile to regenerate the key.
Class GenearteToken extends FOSRestController
{
public getTokenAction(Request $request){
$apiKey = $request->query->get('apikey');
return $apiKey;
}
}

Access to Doctrine during Bundle Initialization

I have a Symfony2 bundle which I want to use database table which stores key value configuration parameters. I want to be able to load a query and cache it for a long time and be able to inject the configuration parameters into symfony2 service container.
Right now I am injecting a service which loads the configuration from doctrine, and calling a get($key) method to retrieve the value for the key I want.
I basically want these configuration options to be available from the symfony2 service container parameter bag.
Is there maybe an event I could tie into or some sort of compiler pass I can use with my bundle to achieve this?
I'll do something like that in your service listener
public function onLateKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$mydata= $this->manager->getRepository('YourBundle:YourTable')->getAll();
$parameters['mydata'] = $mydata;
$request->attributes->add($parameters);
}
In your Controller, you can get your parameters :
$this->container->get('request')->attributes->get('mydata');

Resources