Scenario:
I'm using a bundle (FOSFacebookBundle) that allows me to set parameters for exactly one facebook app in my configuration. Everything works perfectly fine, but now I need to set not only one app, but multiple.
My approach:
I've created a AcmeFacebookBundle, which allows multiple apps to be defined (configuration defined in Acme\FacebookBundle\DependencyInjection\Configuration) in an array like so:
acme_facebook:
apps:
some_competition:
server_url: %acme.facebook.some_competition.server_url%
file: %kernel.root_dir%/../vendor/facebook/php-sdk/src/base_facebook.php
alias: facebook
app_id: %acme.facebook.some_competition.app_id%
secret: % acme .facebook.some_competition.secret%
cookie: true
permissions: [email, user_birthday, user_location]
some_other_competition:
server_url: %acme.facebook. some_other_competition.server_url%
file: %kernel.root_dir%/../vendor/facebook/php-sdk/src/base_facebook.php
alias: facebook
app_id: %acme.facebook. some_other_competition.app_id%
secret: % acme .facebook. some_other_competition.secret%
cookie: true
permissions: [email, user_birthday, user_location]
In Acme\FacebookBundle\DependencyInjection\AcmeFacebookExtension I am then looping through all apps. The idea is to compare the server_url parameter against the current URL and override the fos_facebook configuration with mine.
class AcmeFacebookExtension extends Extension
{
...
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
foreach ($config['apps'] as $app)
{
// check for matching path here?
foreach (array('file', 'app_id', 'secret', 'cookie', 'domain', 'logging', 'culture', 'permissions') as $attribute)
{
$container->setParameter('fos_facebook.' . $attribute, $app[$attribute]);
}
}
}
Problem:
But this is exactly where I'm stuck. Obviously, I have no access to the Request object or the DiC from within AcmeFacebookExtension to do this comparison.
Am I going completely wrong in my approach? Do you have any better idea on how to tackle this problem?
What you want to create is a CompilerPass so that you can manipulate the Container after all other configuration has loaded. These should get you started:
Symfony2: Service Container Compiler Passes
Symfony2: Manipulating Service Parameters and Definitions
Create a CompilerPass
Related
I am trying to override FOSUserBundle's max username length. Seems simple enough but I can't manage to do it.
validation.yml
AppBundle\Entity\User:
properties:
username:
- Length: { max: 5, groups: [CustomRegistration] }
config.yml
fos_user:
[...]
user_class: AppBundle\Entity\User
registration:
form:
validation_groups: [CustomRegistration]
Validation itself works fine. If user provides username longer than 5 characters Symfony shows an error that it should not be longer than 5 characters. The problem is that the HTML form input still uses default FOSUserBundle value (255). Form builder seems to totally ignore validation groups. Is there any way I can tell form builder to use my constraints?
I want to mention that HTML validation works when I use XML format but I need to use YAML and it works only by coincidence so I would not like to rely on such quirk.
I also tried to provide custom type in hope that it will change anything but it didn't. Username input still uses maxlength value of 255. For reference:
getDefaultOptions # AppBundle/Form/RegistrationFormType.php
public function getDefaultOptions(array $options)
{
return [
'data_class' => 'AppBundle\Entity\User',
'validation_groups' => ['Default', 'CustomRegistration']
];
}
config.yml
fos_user:
[...]
user_class: AppBundle\Entity\User
registration:
form:
type: appbundle_registration
services.yml
services:
appbundle.registration.form.type:
class: AppBundle\Form\RegistrationFormType
tags:
- { name: form.type, alias: appbundle_registration }
You should add max-length HTML attribute manually to the field.
Validation has no effect on HTML attributes.
buildForm # AppBundle\Form\RegistrationFormType
$builder->add("username", "text", array("attr" => array("maxlength" => "5")));
See: http://symfony.com/doc/current/reference/forms/types/text.html#max-length
If it doesnt solve the issue, take a look at your template. It can be setted there too..
It seems that Laravel 5 by default applies the CSRF filter to all non-get requests. This is OK for a form POST, but might be a problem to an API that POSTs DELETEs etc.
Simple Question:
How can I set a POST route with no CSRF protection?
Go to app/Http/Middleware/VerifyCsrfToken.php and then enter your routes(for which you want to disable csrf token) in the $except array.
for example:
class VerifyCsrfToken extends BaseVerifier
{
protected $except = [
'/register'
];
}
You can exclude URIs from CSRF by simply adding them to the $except property of the VerifyCsrfToken middleware (app/Http/Middleware/VerifyCsrfToken.php):
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'api/*',
];
}
Documentation: http://laravel.com/docs/5.1/routing#csrf-protection
My hack to the problem:
CSRF is now a "middleware" registered globally in App\Http\Kernel.php. Removing it will default to no CSRF protection (Laravel4 behavior).
To enable it in a route:
Create a short-hand key in your app/Providers/RouteServiceProvider.php :
protected $middleware = [
// ....
'csrf' => 'Illuminate\Foundation\Http\Middleware\VerifyCsrfToken',
];
You can now enable it to any Route:
$router->post('url', ['middleware' => 'csrf', function() {
...
}]);
Not the most elegant solution IMO...
just listen to this. Just before 30 minute i was facing this same problem. Now it solved. just try this.
Goto App -> HTTP-> Kernel
open the kernel file.
there you can see : \App\Http\Middleware\VerifyCsrfToken::class,
just disable this particular code using //
Thatz it! This will work!
So that you can remove the middleware from the API calling (if you want so..)
Reflected the following problem after the user login with the Facebook account: that is redirected to the following route /#_=_
How can I redirect it to this route instead: / Or to more to this /# ?
On the client side, I use the backbone.
Taking #Prynz's idea, we can get further and create a "redirect to page user comes from" this way:
1) In your firewall, take care to remove the following lines:
# security.yml
# ...
logout: true
logout:
path: /logout
target: /
As we will implment the logout ourselves to avoid redirecting to the specified target.
2) Add #Prynz's solution to your security.yml (or config.yml depending on your implementation)
oauth:
resource_owners:
google: "/login/check-google"
facebook: "/login/check-facebook"
twitter: "/login/check-twitter"
sensio_connect: "/login/check-sensio-connect"
login_path: /login
failure_path: /login
default_target_path: /welcome # THIS LINE CONTRIBUTES TO THE MAGIC
oauth_user_provider:
service: app.oauth_user_provider
3) In your routing, add a new controller (here, LoginController) before HWIO's imports:
fuz_app_login:
resource: "#FuzAppBundle/Controller/LoginController.php"
type: annotation
prefix: /
4) Create the corresponding controller:
<?php
namespace Fuz\AppBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
class LoginController
{
/**
* #Route("/login", name="login")
* #Method({"GET"})
*/
public function loginAction(Request $request)
{
if ($this->getUser())
{
// already-logged user accessed /login
return $this->redirect($request->headers->get('referer'));
}
else
{
// redirect to the login page
return $this->forward('HWIOAuthBundle:Connect:connect');
}
}
/**
* #Route("/logout", name="logout")
* #Method({"GET"})
*/
public function logoutAction(Request $request)
{
// we do a manual logout just to redirect the user to where he comes from
$this->container->get('security.context')->setToken(null);
return $this->redirect($request->headers->get('referer'));
}
/**
* #Route("/connect/{service}", name="connect")
* #Method({"GET"})
*/
public function connectAction(Request $request, $service)
{
// we overwrite this route to store user's referer in the session
$this->get('session')->set('referer', $request->headers->get('referer'));
return $this->forward('HWIOAuthBundle:Connect:redirectToService', array('service' => $service));
}
/**
* #Route("/welcome", name="welcome")
* #Method({"GET"})
*/
public function welcomeAction()
{
// on login success, we're redirected to this route...
// time to use the referer we previously stored.
$referer = $this->get('session')->get('referer');
if (is_null($referer))
{
return new RedirectResponse($this->generateUrl('home'));
}
return new RedirectResponse($referer);
}
}
5) relax.
You simply add default_target_path: /whatever/path/you/want to the oauth section under the firewall setup
oauth:
resource_owners:
facebook: '/login/check-facebook'
google: '/login/check-google'
windows: '/login/check-windows'
twitter: '/login/check-twitter'
login_path: /login
failure_path: /login
default_target_path: /whatever/path/you/want
Have a look at https://github.com/hwi/HWIOAuthBundle/issues/89
Redirect using javascript. Add following to the page.
<script>
// Handle facebook callback
if (window.location.hash && window.location.hash == '#_=_') {
window.location.hash = '';
}
</script>
If you want, you can redirect the users to the current page doing this way:
Add in your config.yml
hwi_oauth.target_path_parameter: "target_path"
In your view append the urls with:
&target_path=...
remember you can take the current route name with
app.request.get('_route')
I can login with FOSFacebookBundle and everything works. But, FOSUserBundle does not seem to work because profiler shows Username: anon and Roles: {}. And, there is no user data about logged in user in database. Maybe, I didn't understand how it works. Please, help.
This is my config.yml
fos_user:
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
firewall_name: public
user_class: Trade\TradeBundle\Entity\User
fos_facebook:
file: %kernel.root_dir%/../vendor/facebook/src/base_facebook.php
alias: facebook
app_id: my_app_id
secret: app_secret_key
cookie: true
permissions: [user_about_me]
services:
fos_facebook.auth:
class: Trade\TradeBundle\Security\User\Provider\FacebookProvider
arguments:
facebook: "#fos_facebook.api"
userManager: "#fos_user.user_manager"
validator: "#validator"
container: "#service_container"
This is my security.yml
security:
factories:
- "%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"
role_hierarchy:
ROLE_ADMIN: ROLE_FACEBOOK
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
my_fos_facebook:
id: fos_facebook.auth
firewalls:
public:
pattern: ^/.*
fos_facebook:
app_url: "app_url"
server_url: "server_url"
login_path: /user/login
check_path: /user/login_check
default_target_path: /
provider: my_fos_facebook
anonymous: true
logout:
handlers: ["fos_facebook.logout_handler"]
The code below does not seem to work because when I log in with facebook setTimeout(goLogIn, 500) function inside if is not called.
function goLogIn(){
window.location.href = "{{ path('user_login_check') }}";
}
function onFbInit() {
if (typeof(FB) != 'undefined' && FB != null ) {
FB.Event.subscribe('auth.statusChange', function(response) {
if (response.session || response.authResponse) {
setTimeout(goLogIn, 500);
} else {
window.location.href = "{{ path('_security_logout') }}";
}
});
}
}
These are my controller actions:
/**
* #Route("/user/login", name = "user_login")
*/
public function loginAction()
{
}
/**
* #Route("/user/login_check", name = "user_login_check")
*/
public function loginCheckAction()
{
}
In order for FOSUserBundle to work together with FOSFacebookBundle, you need to specify a specific login route just for the facebook login at security.yml:
public:
fos_facebook:
check_path: /loginFacebook
Of course you are going to need to point that route correctly at routing.yml:
_security_check:
pattern: /loginFacebook
Then you need to change the check URL on the facebook javascript:
function goLogIn(){
window.location.href = "{{ path('_security_check') }}";
}
The last thing is to create the controller and the action for the new route (this is very important, otherwise it's not going to work) and leave it empty:
public function loginFacebookAction()
{
return array();
}
Of course you are going to need to adapt this to your needs, like where I use the DefaultController, you, apperantly, use the UserController.
Hope it helps.
I would guess you are storing empty data because of your permissions request:
permissions: [user_about_me]
I suggest changing to the tutorial recommended:
permissions: [email, user_birthday, user_location]
I'm facing big issue while implementing FOSFacebookBundle.
I followed the docs and have following situation:
* when user clicks login a popup appears
* after user grants permission to the app, FB button is being changed (to Logout)
However, my custom provider is not called (only a constructor is called) - yes, I use a noobish debug method (creating empty files with the name of the class method :-)).
Anybody has any suggestion why? Any tips?
Edit
After some time of trying to solve that issue, I feel I'm lost.
Once again, here's my configuration:
app/config/config.yml:
fos_facebook:
file: %kernel.root_dir%/../vendor/facebook/src/base_facebook.php
alias: facebook
app_id: xxx
secret: xxx
cookie: true
permissions: [email, user_location]
app/config/routing.yml:
_security_login:
pattern: /login
defaults: { _controller: TestBundle:Main:login }
_security_check:
pattern: /login_check
defaults: { _controller: TestBundle:Main:loginCheck }
_security_logout:
pattern: /logout
defaults: { _controller: TestBundle:Main:logout }
app/config/security.yml
security:
factories:
-"%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"
providers:
my_fos_facebook_provider:
id: my.facebook.user
fos_userbundle:
id: fos_user.user_manager
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
login_path: /login
check_path: /login_check
logout: true
anonymous: true
public:
pattern: ^/.*
fos_facebook:
app_url: "http://www.facebook.com/apps/application.php?id=xxx"
server_url: "http://symfonytest.com.dev/app_dev.php/"
login_path: /login
check_path: /login_check
provider: my_fos_facebook_provider
default_target_path: /
anonymous: true
logout: true
I'm also implementing code into twig template as shown in docs (also implemented snippet from #Matt).
I have the same workflow as you and my custom user provider is called correctly and everything is working fine.
The first thing that you need to check is: do you have a JavaScript script that redirects the user to the login_check route after it has successfully login into Facebook via the popup? This is important because calling the login_check route after a valid authentication will trigger the security mechanism of Symfony2 that will call the FOSFacebookBundle special security code that will then call your own custom user provider. I think you may be just missing this small piece.
Here the pieces of JavaScript code required to make it work (using jQuery):
$(document).ready(function() {
Core.facebookInitialize();
});
var Core = {
/**
* Initialize facebook related things. This function will subscribe to the auth.login
* facebook event. When the event is raised, the function will redirect the user to
* the login check path.
*/
facebookInitialize = function() {
FB.Event.subscribe('auth.login', function(response) {
Core.performLoginCheck();
});
};
/**
* Redirect user to the login check path.
*/
performLoginCheck = function() {
window.location = "http://localhost/app_dev.php/login_check";
}
}
I put here my security.yml just to help you check for differences with your own file:
security:
factories:
- "%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"
providers:
acme.facebook_provider:
# This is our custom user provider service id. It is defined in config.yml under services
id: acme.user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
public:
pattern: ^/
fos_facebook:
app_url: "http://www.facebook.com/apps/application.php?id=FACEBOOK_APP_ID"
server_url: "http://localhost/app_dev.php/"
default_target_path: /
login_path: /login
check_path: /login_check
provider: acme.facebook_provider
anonymous: true
logout: true
And my service definition for the custom user provider we use:
services:
acme.user_provider:
class: Application\AcmeBundle\Security\User\Provider\UserProvider
arguments:
facebook: "#fos_facebook.api"
entityManager: "#doctrine.orm.entity_manager"
validator: "#validator"
You also need to create a new route for the /login_check, /login and /logout paths. Those route will be hooked by Symfony2 for the security process. Here an example of the implementation of the actions in a controller called MainController in my cases:
<?php
namespace Application\AcmeBundle\Controller;
use ...;
class MainController extends Controller
{
/**
* This action is responsible of displaying the necessary informations for
* a user to perform login. In our case, this will be a button to connect
* to the facebook API.
*
* Important notice: This will be called ONLY when there is a problem with
* the login_check or by providing the link directly to the user.
*
* #Route("/{_locale}/login", name = "_security_login", defaults = {"_locale" = "en"})
*/
public function loginAction()
{
if ($this->request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $this->request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $this->request->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('AcmeBundle:Main:login.html.twig', array(
'error' => $error
));
}
/**
* This action is responsible of checking if the credentials of the user
* are valid. This will not be called because this will be intercepted by the
* security component of Symfony.
*
* #Route("/{_locale}/login_check", name = "_security_check", defaults = {"_locale" = "en"})
*/
public function loginCheckAction()
{
// Call intercepted by the Security Component of Symfony
}
/**
* This action is responsible of login out a user from the site. This will
* not be called because this will be intercepted by the security component
* of Symfony.
*
* #Route("/{_locale}/logout", name = "_security_logout", defaults = {"_locale" = "en"})
*/
public function logoutAction()
{
return $this->redirect('index');
}
}
Hope this help, if you have more questions or I misunderstand something from your problem, don't hesitate to leave a comment.
Regards,
Matt