I am getting myself into symfony and actually I am stucked on authorization.
I would like to forward a user to a specific route, depending on its role.
Oh, am using FOS Bundle.
services.yaml:
LogoutListener:
class: App\Listeners\LogoutListener
arguments:
userManager: "#fos_user.user_manager"
security.yaml:
firewalls:
main:
....
logout:
handlers: [logoutlistener]
ListenerClass:
namespace App\Listeners;
use FOS\UserBundle\Model\UserManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
class LogoutListener implements LogoutHandlerInterface {
protected $userManager;
/**
* LogoutListener constructor.
* #param UserManagerInterface $userManager
*/
public function __construct(UserManagerInterface $userManager){
$this->userManager = $userManager;
}
public function logout(Request $request, Response $response, TokenInterface $token) {
die('user logged out');
}
}
It always gives me:
InvalidArgumentException
Invalid service "LogoutListener": method "App\Listeners\LogoutListener::__construct()" has no argument type-hinted as "userManager". Check your service definition.
Any hint for me?
regards deAndro
Config has changed a bit with Symfony 4. This should do the trick:
services.yaml
services:
App\Listeners\LogoutListener:
security.yaml
firewalls:
main:
....
logout:
handlers: [App\Listeners\LogoutListener]
ListenerClass
namespace App\Listeners;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
class LogoutListener implements LogoutHandlerInterface {
protected $userManager;
public function __construct(UserManagerInterface $userManager = null)
{
$this->userManager = $userManager;
}
public function logout(Request $Request, Response $Response, TokenInterface $Token) {
die('user logged out');
}
}
Related
In Lumen can we use Blade in the Lumen provider?
Target class [blade.compiler] does not exist.
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class RolesServiceProvider extends ServiceProvider
{
/**
* #return void
*/
public function register()
{
}
/**
* #return void
*/
public function boot()
{
Blade::directive('role', function ($role) {
return "<?php if(auth()->check() &&
auth()->user()->hasRole({$role})) :";
});
Blade::directive('endrole', function ($role) {
return "<?php endif; ?>";
});
}
}
In your scenario, it's happening because I believe that you forgot to register the Illuminate\View\ViewServiceProvider class.
Also, when registering the Provider, make sure to use $app->configure('view') in your bootstrap/app.php or $this->app->configure('view') from your Service Provider to configure your view configuration. Because the view service provider doesn't load the configuration itself.
You can check how the view component is loaded in a Lumen application.
I did like the following
if (!$this->app->bound('view')) {
// Lumen doesn't load the view config by default
$this->app->configure('view');
$this->app->register(ViewServiceProvider::class);
}
Or you can do the loadComponent thing as lumen does. And it will solve the issue you stated.
I'm creating a SPA backed by Symfony and ApiPlatform so I want to always load my main route despite the real path of the URL.
I want something like this:
/**
* {#inheritdoc}
*/
class DefaultController extends Controller
{
/**
* #Route("/*", name="homepage")
*
* #return Response
*/
public function indexAction(): Response
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig');
}
}
In my intentions, also if the URL is something like /path/to/the/spa/page I want to anyway load the DefaultController::indexAction()route.
How to do this? (obviously the provided example doesn't work).
Ok, I've found the solution after an "illumination".
I remembered that there is the possibility to rewrite all URL adding or removing the trailing slash
Reading that article I saw this:
class RedirectingController extends Controller
{
/**
* #Route("/{url}", name="remove_trailing_slash",
* requirements={"url" = ".*\/$"})
*/
public function removeTrailingSlash(Request $request)
{
// ...
}
}
So, to intercept all URL despite the path, my DefaultController::indexAction() becomes this:
class DefaultController extends Controller
{
/**
* #Route("/{url}",requirements={"url"=".*"}, name="homepage")
*
* #return Response
*/
public function indexAction(): Response
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig');
}
}
Now all URL are all handled by DefaultController::indexAction() despite the URL path.
I would recommend you to use symfony's event system instead.
Subscribe to either kernel.request or kernel.router events.
In case of kernel.request you have to overtake the Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()
which priotiry is 32 (use 33 at least).
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SpaSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 33],
];
}
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
if (!$request->isXmlHttpRequest()) {
$html = $this->twig->render('spa.html.twig', [
'uri' => $request->getUri(),
]);
$response = new Response($html, Response::HTTP_OK);
$event->setResponse($response);
}
}
}
In case of kernel.router use priority 1 at least.
You can use the php bin/console debug:event-dispatcher command to find out which listeners are registered for events and their priorities.
I have created a plugin called WdContactForm in cakephp3.
Inside a function of the plugin controller I need to send an email
But returns me this error:
Could not send email: Declaration of WdContactForm\Controller\AppController::beforeFilter() should be compatible with App\Controller\AppController::beforeFilter()
I have already read about adding:
use Cake\Event\Event;
I have already done it.
But I can't fix the problem
This is my controller inside plugin:
namespace WdContactForm\Controller;
use Cake\Event\Event;
use WdContactForm\Controller\AppController;
use Cake\Core\App;
use Cake\Utility\Security;
use Cake\Utility\Inflector;
use Cake\ORM\TableRegistry;
use Cake\Core\Configure;
use Cake\Mailer\Email;
class ContactFormsController extends AppController
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Admin');
}
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
//other functions
//...
}
public function send_email()
{
$email = new Email('default');
$email->from(['test#test.it' => 'My Site'])
->to('test#test.it')
->subject('About')
->send('My message');
}
}
And this is my AppController inside the plugin:
namespace WdContactForm\Controller;
use Cake\Event\Event;
use App\Controller\AppController as BaseController;
class AppController extends BaseController
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
}
}
How can I fix it?
I have the below code where I am trying to re-encode passwords as users log in (the database has bee migrated form a legacy website). However, I'm not sure what I'm doing wrong as I keep getting errors:
Attempted to call an undefined method named "forward" of class "AppBundle\Service\HubAuthenticator".
I have set things up as follows:
security.yml
security:
encoders:
AppBundle\Entity\Member:
id: club.hub_authenticator
services.yml
services:
//This should be central service than then calls the second
club.hub_authenticator:
class: AppBundle\Service\HubAuthenticator
club.password_rehash:
class: AppBundle\Service\PasswordRehash
Hubauthenticator.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
function __construct($cost=13)
{
parent::__construct($cost);
}
function isPasswordValid($encoded, $raw, $salt)
{
// Test for legacy authentication (and conditionally rehash the password stored in the database if true)
if ($this->comparePasswords($encoded, sha1("saltA".$raw."saltB"))) {
$this->forward('club.password_rehash:rehash');
}
// Test for Symfony's Bcrypt authentication (any passwords just rehashed in previous step should work here)
if (parent::isPasswordValid($cost=13, $encoded,$raw,$salt)) return true ;
}
}
PasswordRehash.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class PasswordRehash extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder
{
// Customises BCryptPasswordEncoder class to use legacy SHA method
function rehash($member, $raw, $salt)
{
//Salt is null as Symfony documentation says it is better to generate a new one
parent::encodePassword($member->getPlainPassword, $salt=null ) ;
}
}
Some other previous attempts for completeness:
My guess is that the problem is that I am misunderstanding what objects are available to me. My understanding is that the user hasn't been authenticated at this point so have tried and removed the below attempts:
Trying to inject the $member into the HubAuthenticator service:
function __construct($cost=13)
{
parent::__construct($cost, \Member $member);
}
When trying to get the plainpassword to rehash:
$this->get('security.context')->getToken()->getUser()->getPlainPassword();
In your services, you can only access what dependencies you've injected.
So, to access the current user object, you need to pass it as argument:
service:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage" ]
Constructor:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
private $storage;
function __construct($cost = 13, TokenStorageInterface $storage)
{
parent::__construct($cost);
$this->storage = $storage;
// Now you can use:
// $user = $this->storage->getToken()->getUser();
}
}
Then, to access the second service, same way, inject it.
Add it to the service arguments:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage", "#club.password_rehash" ]
Add it to your constructor:
private $storage;
private $passwordRehash
function __construct($cost = 13, TokenStorageInterface $storage, PasswordRehash $passwordRehash)
{
parent::__construct($cost);
$this->storage = $storage;
$this->passwordRehash = $passwordRehash;
// Now you can use:
// $this->passwordRehash->rehash(...);
}
Hope this helps you.
I start with a seeded database and am trying to reseed the database between unit tests in Laravel 5. In Laravel 4 I understand you could simply use Illuminate\Support\Facades\Artisan and run the commands
Artisan::call('migrate');
Artisan::call('db:seed');
or you supposedly could do:
$this->seed('DatabaseSeeder');
before every test. In Laravel 5 this appears to have been replaced by
use DatabaseMigrations;
or
use DatabaseTransactions;
I have tried using these and have managed to get the tests to migrate the database; however, it doesn't actually reseed the data in the tables. I have read through several forums complaining about this and have tried several different approaches calling these from the TestCase and inside every Test...adding the
$this->beforeApplicationDestroyed(function () {
Artisan::call('migrate');
Artisan::call('migrate:reset');
Artisan::call('db:seed');
DB::disconnect();
});
to the TestCase.php tearDown()...
I have also tried adding
$this->createApplication();
to a method called in every test from TestCase.php
Sometimes it just wipes my tables out completely. Nothing I am finding on Laravel's site or in blogs seems to work. Part of it is probably because I'm probably trying Laravel 4 methods in Laravel 5. Is there any way to do this in Laravel 5?
My code for the testcase.php looks like:
<?php
use Illuminate\Support\Facades\Artisan as Artisan;
class TestCase extends Illuminate\Foundation\Testing\TestCase{
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
protected $baseUrl = 'http://localhost';
public function initializeTests(){
$this->createApplication();
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
$this->session(['test' => 'session']);
$this->seed('DatabaseSeeder');
}
public function tearDown()
{
Mockery::close();
Artisan::call('migrate:reset');
$this->artisan('migrate:reset');
Artisan::call('migrate:rollback');
$this->artisan('migrate:rollback');
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
DB::disconnect();
foreach (\DB::getConnections() as $connection) {
$connection->disconnect();
}
$this->beforeApplicationDestroyed(function () {
Artisan::call('migrate:reset');
$this->artisan('migrate:reset');
Artisan::call('migrate:rollback');
$this->artisan('migrate:rollback');
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
DB::disconnect();
foreach (\DB::getConnections() as $connection) {
$connection->disconnect();
}
});
$this->flushSession();
parent::tearDown();
}
public function getConnection()
{
$Connection = mysqli_connect($GLOBALS['DB_DSN'], $GLOBALS['DB_USERNAME'], $GLOBALS['DB_PASSWORD'], $GLOBALS['DB_DATABASE']);
$this->createDefaultDBConnection();
return $this->Connection;
}
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
/**
* Magic helper method to make running requests simpler.
*
* #param $method
* #param $args
* #return \Illuminate\Http\Response
*/
public function __call($method, $args)
{
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete']))
{
return $this->call($method, $args[0]);
}
throw new BadMethodCallException;
}
/**
* Create a mock of a class as well as an instance.
*
* #param $class
* #return \Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
}
My Test looks something like
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
class CustomerRegistrationControllerTest extends TestCase
{
use DatabaseMigrations;
protected static $db_inited = false;
protected static function initDB()
{
echo "\n---Customer Registration Controller Tests---\n"; // proof it only runs once per test TestCase class
Artisan::call('migrate');
Artisan::call('db:seed');
}
public function setUp()
{
parent::setUp();
if (!static::$db_inited) {
static::$db_inited = true;
static::initDB();
}
// $this->app->refreshApplication();
$this->artisan('migrate:refresh');
$this->seed();
$this->seed('DatabaseSeeder');
$this->initializeTests();
);
}
public function testSomething()
{
$this->Mock
->shouldReceive('destroy')
->with('1')
->andReturn();
$this->RegistrationController->postRegistration();
// $this->assertResponseStatus(200);
}
}
Just run this:
$this->artisan('migrate:refresh', [
'--seed' => '1'
]);
To avoid changes to the database persisting between tests add use DatabaseTransactions to your tests that hit the database.
Why not create your own command like db:reset.
This command either truncate all your tables or drop/create schema and then migrate.
In your test you then use: $this->call('db:reset') in between your tests