Orchestra Testbench current route always return null - php

I'm working on a Laravel package it is working when I use it in my Laravel project, but when I want to test it with Orchestra Testbench I always get current route null inside middlware.
Test directory on Github: https://github.com/yoeunes/larafast/tree/master/tests
Base TestCase:
class TestCase extends Orchestra\Testbench\TestCase
{
protected function getEnvironmentSetUp($app)
{
$kernel = app('Illuminate\Contracts\Http\Kernel');
$kernel->pushMiddleware(\Illuminate\Session\Middleware\StartSession::class);
$kernel->pushMiddleware(\Yoeunes\Larafast\Middlewares\BlacklistRoutes::class);
}
}
WebControllerTest:
class WebControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
/** #var \Illuminate\Routing\Router $router */
$router = $this->app['router'];
$router->resource('lessons', 'Yoeunes\Larafast\Tests\Stubs\Controllers\Web\LessonController');
}
/** #test */
public function it_show_create_page()
{
/** #var TestResponse $response */
$response = $this->call('get', '/lessons/create');
dd($response);
$response->assertSuccessful();
$response->assertSee('<title>lessons create | Larafast</title>');
$response->assertSee('<i class="fa fa-plus-circle"></i> lessons create');
$response->assertSee('<form method="POST" action="http://localhost/lessons" accept-charset="UTF-8" enctype="multipart/form-data">');
}
}
BlacklistRoutes Middleware:
class BlacklistRoutes
{
public function handle($request, Closure $next)
{
dd(app('router')->getCurrentRoute()); // always get null
if (null !== ($route = app('router')->getCurrentRoute())
&& is_a($controller = $route->getController(), Controller::class)
&& in_array($route->getActionMethod(), $controller->getBlacklist(), true)) {
throw new BlacklistRouteException();
}
return $next($request);
}
}

According to Orchestra Testbench Github repository :
AFAIK global middleware is resolved before route is resolved,
therefore getCurrentRoute() shouldn't be available yet.
So the resolve my problem I can access to current route from within the controller constructor like this:
public function __construct()
{
$this->middleware(function ($request, Closure $next) {
if (null !== ($route = app('router')->getCurrentRoute())
&& is_a($controller = $route->getController(), Controller::class)
&& in_array($route->getActionMethod(), $controller->getBlacklist(), true)) {
throw new BlacklistRouteException();
}
});
}

Related

Call to undefined method Symfony\Component\HttpFoundation\Response::withCookie()

Laravel: 6.18.1
PHP: 7.4
Middleware in laravel is creating this error
{
"message": "Call to undefined method Symfony\\Component\\HttpFoundation\\Response::withCookie()",
"exception": "Symfony\\Component\\Debug\\Exception\\FatalThrowableError",
Code which is giving me error
if (!$request->hasCookie('ppl') || ($request->hasCookie('ppl') && $ppl_cookie->ppl_id != $ppl->ppl_id)) {
if (Auth::check()) {
Event::dispatch('ppl.updated', [Auth::user(), $ppl]);
}
return $next($request)->withCookie(cookie()->forever('ppl', $ppl));
}
I don't understand issue. cookie is not stored in browser
Edit
Middleware class
<?php
namespace App\Http\Middleware;
use Closure;
use App\System\Models\People;
use App;
use Event;
use Auth;
use Illuminate\Support\Facades\URL;
use Session;
class VerifyPeople
{
protected $app;
public function __construct()
{
$this->app = app();
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$People_cookie = json_decode($request->cookie('People'));
if (!empty($request->route()) && in_array($request->route()->uri(), $this->excepts)) {
return $next($request);
}
if ($request->getHost()) {
$domain_url = cleanUrl($request->getHost());
$People = People::where('People_url', '=', $domain_url)->remember(LONG_TERM_CACHE_TIMEOUT)->cacheTags(TAG_LONGTERM_DATA)->first();
} elseif ($request->hasCookie('People')) {
$People = $People_cookie;
}
if (empty($People)) {
$People = People::where("People_id", People::DEFAULT_People)->remember(LONG_TERM_CACHE_TIMEOUT)->cacheTags(TAG_LONGTERM_DATA)->first();
}
$this->app->singleton('People', function () use ($People) {
return $People;
});
if (!$request->hasCookie('People') || ($request->hasCookie('People') && $People_cookie->People_id != $People->People_id)) {
if (Auth::check()) {
Event::dispatch('People.updated', [Auth::user(), $People]);
}
return $next($request)->withCookie(cookie()->forever('People', $People));
}
return $next($request);
}
}
In the middleware when you do dd($next($request)); it is supposed print an instance of Illuminate\Http\Response which can access to ResponseTrait and withCookie method within the trait..
In your case, the instance of Symfony\Component\HttpFoundation\Response is giving error which is extended by Illuminate\Http\Response but doesn't use ResponseTrait. That's the reason withCookie method didn't found.
There may be several reasons such as, before this middleware is executed - another middleware is modifying default response with Symfony response.
Here is the withCookie method.
public function withCookie($cookie)
{
if (is_string($cookie) && function_exists('cookie')) {
$cookie = call_user_func_array('cookie', func_get_args());
}
$this->headers->setCookie($cookie);
return $this;
}
What it does is calling setCookie method of headers. What you may do is;
Replace
return $next($request)->withCookie(cookie()->forever('People', $People));
with
$response = $next($request);
$response->headers->setCookie(cookie()->forever('People', $People));
return $response;
While looking; i found a similar case to yours

How to authenticate specific route to user with specific role in laravel Milldeware

I have multiple users with multiple permissions. A user can belong to the only single role but that role can have multiple permissions like create, read, update, delete. And I have a RoleMiddleware. I am authenticating the user in roleMiddleware. But how can I protect routes in RoleMiddleware against a specific user?
For Example, I have a route create-case which can only be accessed by the operator or by Admin else everyone redirects to 404 error how Can I deal with it in RoleMiddleware.
I have written basic code for authentication where every user with their roles is authenticated but I am getting how can I code in middleware so ever route when a user hits it may go to the RoleMiddleware where middleware Authenticate route to the Role and then give him the access.
Role Middleware
class RoleMiddleware
{
public function handle($request, Closure $next, $permission = null)
{
if (Auth::check() === false)
{
return redirect('login');
}
elseif (Auth::check() === true)
{
$roles = Role::all()->pluck('slug');
if (is_null($request->user()) )
{
abort(404);
}
if (!$request->user()->hasRole($roles))
{
abort(404);
}
if ($request->user())
{
if ($request->user()->hasRole($roles))
{
return $next($request);
}
}
}
}
}
Case Controller:
<?php
namespace App\Http\Controllers\Cases;
use App\Http\Controllers\Controller;
use App\Http\Requests\CaseStoreRequest;
use Illuminate\Support\Facades\Auth;
use Session;
class CaseController extends Controller
{
use DropzoneFileUploadTraits;
public function __construct()
{
$this->middleware('role');
}
public function index()
{
$data['portal'] = Portal::all();
$data['operators'] = Operator::all();
return view('case', $data);
}
public function caseList()
{
$user = new User();
$isAdmin = $user->isAdmin();
$loggedIn = Auth::id();
$cases = Cases::with('patients', 'portal')
->when(!$isAdmin, function ($query) use ($loggedIn) {
return $query->where('user_id', $loggedIn);
})->orderBy('created_at', 'desc')->get();
$data['cases'] = $cases;
return view('case_list', $data);
}
}
Route:
Route::get('create-case', 'Cases\CaseController#index')->name('create-case');
Route::post('case-submit', 'Cases\CaseController#caseSubmit')->name('case-submit');
Route::post('edit-patient-case-submit', 'Cases\CaseController#editPatientCaseSubmit')->name('edit-patient-case-submit');
Route::get('case-list', 'Cases\CaseController#caseList')->name('case-list');
Best way to do that in a clean manner would be to create policies on the targeted entities.
Laravel policies allow you to :
Bind a route authorization logic to a policy action
Easily call a policy action result from anywhere else in the project (views, controllers and so on).
The subject is well covered in Laravel documentation so I suggest you go there and take a look. Do not forget to register the policy and bind it to your model.
Apart from that this should do the trick.
class CasePolicy
{
use HandlesAuthorization;
public function create(User $user){
$roles = ['operator','Admin']
return $user->hasRole($roles);
}
}
Then in your route file :
Route::get('create-case', 'Cases\CaseController#index')->name('create-case')->middleware('can:create,App\Case');
I haved just learned and implement Gate and Policy hope this is correct Because its working for me. Great concept thanks.
Route::get('create-case', 'Cases\CaseController#index')->name('create-case')->middleware('can:create-case,App\Model\Case');
Gate:
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
User::class => CreateCase::class
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('create-case','App\Policies\CreateCase#create_case');
}
}
Policy
class CreateCase
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function create_case(User $user){
if($user->hasRole(['admin']) ||$user->hasRole(['operator']) && $user->hasPermissionTo('create')){
return true;
}else
return false;
}
}

Check user in every controller in Symfony

I have a user object that has a property 'enabled'. I want every action to first check if the user is enabled before continuing.
Right now I have solved it with a Controller that every other controller extends, but using the setContainer function to catch every Controller action feels really hacky.
class BaseController extends Controller{
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
$user = $this->getUser();
// Redirect disabled users to a info page
if (!$user->isEnabled() && !$this instanceof InfoController) {
return $this->redirectToRoute('path_to_info');
}
}
I have tried building this using a before filter (http://symfony.com/doc/current/event_dispatcher/before_after_filters.html), but could not get the User object..any tips?
EDIT:
This is my solution:
namespace AppBundle\Security;
use AppBundle\Controller\AccessDeniedController;
use AppBundle\Controller\ConfirmController;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class UserEnabledListener
{
private $tokenStorage;
private $router;
public function __construct(TokenStorage $tokenStorage, Router $router)
{
$this->tokenStorage = $tokenStorage;
$this->router = $router;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure.
* This is not usual in Symfony but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
$controller = $controller[0];
// Skip enabled check when:
// - we are already are the AccessDenied controller, or
// - user confirms e-mail and becomes enabled again, or
// - Twig throws error in template
if ($controller instanceof AccessDeniedController ||
$controller instanceof ConfirmController ||
$controller instanceof ExceptionController) {
return;
}
$user = $this->tokenStorage->getToken()->getUser();
// Show info page when user is disabled
if (!$user->isEnabled()) {
$redirectUrl = $this->router->generate('warning');
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
}
}
EDIT 2:
Ok so turns out checking for each controller manually is really bad, as you will miss Controllers from third party dependencies. I'm going to use the Security annotation and do further custom logic in a custom Exception controller or template etc.
You can use an event listener to listen for any new request.
You'll need to inject the user and then do your verification:
<service id="my_request_listener" class="Namespace\MyListener">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
<argument type="service" id="security.token_storage" />
</service>
Edit: Here is a snippet to give an example
class MyRequestListener {
private $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->getRequest()->isMasterRequest()) {
// don't do anything if it's not the master request
return;
}
if ($this->tokenStorage->getToken()) {
$user = $this->tokenStorage->getToken()->getUser();
//do your verification here
}
}
In your case I would use the #Security annotation, which can be very flexible if you use the expression language.
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* #Security("user.isEnabled()")
*/
class EventController extends Controller
{
// ...
}
In the end it's only 1 line in each of your controller files, and it has the advantage of being very readable (a developer new to the project would know immediately what is going on without having to go and check the contents of a BaseController or any potential before filter...)
More documentation on this here.
You can override also getuser() function in your BaseController also.
/**
* Get a user from the Security Token Storage.
*
* #return mixed
*
* #throws \LogicException If SecurityBundle is not available
*
* #see TokenInterface::getUser()
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
// Redirect disabled users to a info page
if (!$user->isEnabled() && !$this instanceof InfoController) {
return $this->redirectToRoute('path_to_info');
}
return $user;
}

Slim 3 how to use logger inside middleware function

I have written this router after creating everything using the official:
$ php composer.phar create-project slim/slim-skeleton [my-app-name]
this is my routes.php file
<?php
require_once(__DIR__."/../bootstrap.php");
// Routes
class OwnsPost
{
/**
* Example middleware invokable class
*
* #param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
* #param \Psr\Http\Message\ResponseInterface $response PSR7 response
* #param callable $next Next middleware
*
* #return \Psr\Http\Message\ResponseInterface
*/
public function __invoke($request, $response, $next)
{
if($request->getQueryParams() && $request->getQueryParams()['pid']){
$pid = intval($request->getQueryParams()['pid']);
if($pid == 0){
$this->logger->info("illegal pid call");
return false;
}
$cpost = get_post($pid);
if($cpost->post_author != get_current_user()){
$this->logger->info("wrong current user, tried accessing postid " . $cpost->ID . " with user ". get_current_user());
return false;
}
}else{
$this->logger->info("illegal pid call");
return false;
}
// $response->getBody()->write('BEFORE');
$response = $next($request, $response);
// $response->getBody()->write('AFTER');
return $response;
}
}
$app->get('/campaignedit/setcharitable/{id}', function ($request, $response, $id) {
// Sample log message
$this->logger->info("setcharitable '/' route " . $id);
// Render index view
return $this->renderer->render($response, 'index2.php', $id);
})->add( new OwnsPost() );
$this->logger works in the routing section, but not in the middleware section.
I get an
Fatal error: Call to a member function info() on a non-object in
/var/www/html/japi/src/routes.php on line 33
There is no member variable $logger in your middleware class. So, first add one.
protected $logger;
Next, add a constructor that accepts $logger as an argument.
public function __construct($logger)
{
$this->logger = $logger;
}
Last, when you initialize your middleware, pass the instance of your Logger.
$ownsPost = new OwnsPost($this->logger);
I need obviously to call to
global $app

Symfony2: Execute some code after every action

I recently started a project in Symfony2 and I need to run some methods before and after every action to avoid code redundancy (like preDispatch/postDispatch from Zend Framework and PreExecute/PostExecute from Symfony1).
I created a base class from which all the controllers are inherited,
and registered an event listener to run controller's preExecute() method before running requested action, but after reading tons of documentation and questions from here I still can't find how to run postExecute().
Foo/BarBundle/Controller/BaseController.php:
class BaseController extends Controller {
protected $_user;
protected $_em;
public function preExecute() {
$user = $this->get('security.context')->getToken()->getUser();
$this->_user = $user instanceof User ? $user : null;
$this->_em = $this->getDoctrine()->getEntityManager();
}
public function postExecute() {
$this->_em->flush();
}
}
Foo/BarBundle/Controller/FooController.php:
class FooController extends BaseController {
public function indexAction() {
$this->_user->setName('Eric');
$this->_em->persist($this->_user);
}
}
Foo/BarBundle/EventListener/PreExecute.php:
class PreExecute {
public function onKernelController(FilterControllerEvent $event) {
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$controllers = $event->getController();
if (is_array($controllers)) {
$controller = $controllers[0];
if (is_object($controller) && method_exists($controller, 'preExecute')) {
$controller->preExecute();
}
}
}
}
}
There is a discussion of this here and this particular example by schmittjoh may lead you in the right direction.
<?php
class Listener
{
public function onKernelController($event)
{
$currentController = $event->getController();
$newController = function() use ($currentController) {
// pre-execute
$rs = call_user_func_array($currentController, func_get_args());
// post-execute
return $rs;
};
$event->setController($newController);
}
}

Categories