Slim 3 how to use logger inside middleware function - php

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

Related

Action executing twice when middleware is added Slim framework

I'm building a simple app based on the excellent Slim 4 Tutorial https://odan.github.io/2019/11/05/slim4-tutorial.html
The structure is still fairly similar to the tutorial.
Every time I call any of my endpoints using Postman it executes my Actions twice (ie: \App\Action\UserGetAction) for a Get. I can confirm this is happening in my logs.
if I comment out the AuthMiddleware file in config/middleware.php the duplicate action stops happening.
Any ideas?
I have created my own Auth middleware and added it in the config/middleware.php file:
use Selective\BasePath\BasePathMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
use SlimSession\Helper;
use App\Factory\SessionFactory;
use App\Factory\AuthMiddleware;
return function (App $app) {
// Parse json, form data and xml
$app->addBodyParsingMiddleware();
// Add the Slim built-in routing middleware
$app->addRoutingMiddleware();
$app->add(BasePathMiddleware::class);
// Catch exceptions and errors
$app->add(ErrorMiddleware::class);
$app->add(AuthMiddleware::class); // <--- here
$loggerFactory = $app->getContainer()->get(\App\Factory\LoggerFactory::class);
$logger = $loggerFactory->addFileHandler('error.log')->createInstance('error');
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $logger);
};
for simplicity I have stripped out pretty much everything in the AuthMiddleware:
namespace App\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
use GuzzleHttp\Client;
use Exception;
use App\Factory\LoggerFactory;
class AuthMiddleware
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerFactory $logger)
{
$this->logger = $logger
->addFileHandler('user_edit.log')
->addConsoleHandler()
->createInstance('user_edit');
}
/**
* Example middleware invokable class
*
* #param ServerRequest $request PSR-7 request
* #param RequestHandler $handler PSR-15 request handler
*
* #return Response
*/
public function __invoke(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
return $handler->handle($request);
}
} //Class
here are my routes in config/routes.php:
$app->post('/users', \App\Action\UserCreateAction::class);
$app->map(['PUT', 'PATCH'],'/users/{id}[/{system}]', \App\Action\UserUpdateAction::class);
$app->delete('/users/{id}[/{system}]', \App\Action\UserDeleteAction::class);
$app->get('/users/{id}[/{system}]', \App\Action\UserGetAction::class);
$app->get('/extid/{id}[/{system}]', \App\Action\ExtidGetAction::class);
I have confirmed that I'm only running $app-run() once --- at the end of my index.php
require __DIR__ . '/../config/bootstrap.php';
$app->run();
There is error in __invoke method of your middleware.
You call handler queue twice:
public function __invoke(Request $request, RequestHandler $handler): Response
{
// here you call handler queue first time
// so you get response
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
// here you call handle queue for the second time
// so you get the second duplicated version of response
// this is the reason why you actions called twice
return $handler->handle($request);
}
One of the solutions is below:
public function __invoke(Request $request, RequestHandler $handler): Response
{
// here you call handler queue first time
// so you get response
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
// return response you already have
return $response;
}

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 do I implement CSRF protection with Slim 4 and slim/csrf?

Slim 4 is already here and I am trying to move to Slim 4. Everything is great, but CSRF returns an error when i try to implement it. I tried the simplest setup, but I get this error:
Message: Argument 2 passed to Slim\Csrf\Guard::__invoke() must be an instance of Psr\Http\Message\ResponseInterface, instance of Slim\Routing\RouteRunner given, called in /Volumes/Web/slim/vendor/slim/slim/Slim/MiddlewareDispatcher.php on line 180
File: /Volumes/Web/slim/vendor/slim/csrf/src/Guard.php
Here is my code:
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Csrf\Guard;
require __DIR__ . '/../vendor/autoload.php';
/**
* Instantiate App
*
* In order for the factory to work you need to ensure you have installed
* a supported PSR-7 implementation of your choice e.g.: Slim PSR-7 and a supported
* ServerRequest creator (included with Slim PSR-7)
*/
$app = AppFactory::create();
$app->add(Guard::class);
// Add Routing Middleware
$app->addRoutingMiddleware();
/*
* Add Error Handling Middleware
*
* #param bool $displayErrorDetails -> Should be set to false in production
* #param bool $logErrors -> Parameter is passed to the default ErrorHandler
* #param bool $logErrorDetails -> Display error details in error log
* which can be replaced by a callable of your choice.
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// Define app routes
$app->get('/', function (Request $request, Response $response, $args) {
$response->getBody()->write('Hello');
return $response;
});
// Run app
$app->run();
Any help is greatly appreciated! Thanks!
The package is not compatible with Slim4. I wrote a wrapper so you can use it.
`
<?php
declare(strict_types=1);
namespace App\Application\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Csrf\Guard as Guard;
class CsrfMiddleware extends Guard implements Middleware
{
/**
* Process middleware
*
* #param ServerRequestInterface $request request object
* #param RequestHandlerInterface $handler handler object
*
* #return ResponseInterface response object
*/
public function process(Request $request, RequestHandler $handler): Response
{
$this->validateStorage();
// Validate POST, PUT, DELETE, PATCH requests
if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
$body = $request->getParsedBody();
$body = $body ? (array) $body : [];
$name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false;
$value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false;
if (!$name || !$value || !$this->validateToken($name, $value)) {
// Need to regenerate a new token, as the validateToken removed the current one.
$request = $this->generateNewToken($request);
$failureCallable = $this->getFailureCallable();
return $failureCallable($request, $handler);
}
}
// Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored
if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) {
$request = $this->generateNewToken($request);
} elseif ($this->persistentTokenMode) {
$pair = $this->loadLastKeyPair() ? $this->keyPair : $this->generateToken();
$request = $this->attachRequestAttributes($request, $pair);
}
// Enforce the storage limit
$this->enforceStorageLimit();
return $handler->handle($request);
}
/**
* Getter for failureCallable
*
* #return callable|\Closure
*/
public function getFailureCallable()
{
if (is_null($this->failureCallable)) {
$this->failureCallable = function (Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$stream = $response->getBody();
$stream->write('CSRF fail');
return $response->withStatus(400);
};
}
return $this->failureCallable;
}
}
`
The relevant bit is:
$app->add(Guard::class);
The signature of middleware callbacks has changed. In Slim/3 it used to be like this:
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
): ResponseInterface
... and then the method had to call $next like $next($request, $response).
In Slim/4 it's like this:
public function __invoke(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
.. and the internal call to $handler is $handler->handle($request).
The library does not seem to have been updated for Slim/4. It declares Slim/3 as dev (?) dependency in composer.json and mentions in README.md. Perhaps it isn't very difficult to either fix the library or write a compatible wrapper on top of it but if you aren't familiar with the overall ecosystem it's probably easier to install a replacement.

Slim\\Route::__invoke() with Middleware is not accepting the right $request & $response

I am currently writing a REST API using Slim Framework 3 and implementing Middleware for basic authentication.
My routing goes like this:
$app->group('/api', function () use ($app, $pdo) {
$this->group('/v1', function () use ($app, $pdo) {
// Guest Routes
$this->group('', function() use ($app, $pdo) {
require_once '../app/api/v1/authentication.php';
});
// Authenticated Routes
$this->group('', function() use ($app, $pdo) {
require_once '../app/api/v1/test.php';
})->add(new \App\Middleware\AuthMiddleware($pdo));
});
});
In the AuthMiddleware class I am using the __invoke method in the following way:
namespace App\Middleware;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
class AuthMiddleware extends Middleware {
/**
* #param Request $request
* #param Response $response
* #param $next
* #return Response
*/
public function __invoke(Request $request, Response $response, $next) {
$response = $next($response, $request);
return $response;
}
}
And I'm getting the following error:
Argument 1 passed to Slim\Route::__invoke() must implement interface Psr\Http\Message\ServerRequestInterface, instance of Slim\Http\Response given
on the following line:
$response = $next($response, $request);
What is happening? any ideas? I've been eating myself over this for 2 hours :(
Thanks a bunch!
Stupidly.. I noticed that on
$response = $next($response, $request);
I reversed the parameters.. should be
$response = $next($request, $response);
Blaahh... my head hurts.

Laravel user capabilities

Within Laravel you can easily define abilities and then hook into them later on a user request regarding to do different actions:
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
But almost all my defined abilities has this part $user->id === $model->user_id in it. I don't like it as it's a kind of repeating a condition over and over which I think could be more abstract.
Most of my defined abilities are according to updating/deleting records, so it would be better if I could make a global condition applied to all of them or if there could be a group ability defining which is like to what we do in routing.
Is there any workaround for it? I really like it DRY.
Everything in Laravel is extendable, that's the power of its service providers.
You can extend the Gate object to a MyCustomGate object and do whatever you want in that object. Here's an example:
MyCustomGate.php
class MyCustomGate extends \Illuminate\Auth\Access\Gate
{
protected $hasOwnershipVerification = [];
/**
* Define a new ability.
*
* #param string $ability
* #param callable|string $callback
* #return $this
*
* #throws \InvalidArgumentException
*/
public function defineWithOwnership($ability, $callback, $foreignUserIdKey = "user_id")
{
// We will add this
$this->hasOwnershipVerification[$ability] = $foreignUserIdKey;
return $this->define($ability, $callback);
}
/**
* Resolve and call the appropriate authorization callback.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return bool
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
// We will assume that the model is ALWAYS the first key
$model = is_array($arguments) ? $arguments[0] : $arguments;
return $this->checkDirectOwnership($ability, $user, $model) && call_user_func_array(
$callback, array_merge([$user], $arguments)
);
}
/**
* Check if the user owns a model.
*
* #param string $ability
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param \Illuminate\Database\Eloquent\Model $model
* #return bool
*/
protected function checkDirectOwnership($ability, $user, $model)
{
if(!isset($this->hasOwnershipVerification[$ability])) {
return true
}
$userIdKey = $this->hasOwnershipVerification[$ability];
// getAuthIdentifier() is just ->id, but it's better in case the pk of a user is different that id
return $user->getAuthIdentifier() == $model->{$userIdKey};
}
}
Then, you will have to tell Laravel to use your gate instead of the default one. You ca do that in your AuthServiceProvider (assuming that it's extending Illuminate\Auth\AuthServiceProvider, just add the following method.
AuthServiceProvider
/**
* Register the access gate service.
*
* #return void
*/
protected function registerAccessGate()
{
$this->app->singleton(\Illuminate\Contracts\Auth\Access\Gate::class, function ($app) {
return new MyCustomGate($app, function () use ($app) {
return $app['auth']->user();
});
});
}
And this way, you can define abilities using defineWithOwnership() method instead of define(). You can still use define() for abilities that don't require ownership verification. There's a third parameter defineWithOwnership() accepts which is $foreignUserIdKey; that's used for the case when a model has a different field for the user id.
Note: I wrote the code on the fly and did not try it, it may have errors, but you get the idea.
I checked your question quite a bit, but I've found no "easy" way to do it.
Instead, what I would probably do is this:
<?php
namespace App\Policies;
use App\User;
use App\Post;
trait CheckOwnership {
protected function checkOwnership($user, $model) {
$owned = $user->id === $model->user_id;
if ($owned === false)
throw new NotOwnedException;
}
}
class PostPolicy
{
use CheckOwnership;
public function update(User $user, Post $post)
{
try {
$this->checkOwnership($user, $post);
//continue other checks
} catch (NotOwnedException $ex) {
return false;
}
}
}
Add this function to your AuthServiceProvider
public function defineAbilities(array $abilities, $gate)
{
foreach($abilities as $name => $model){
$gate->define($name, function ($user, $model){
return $user->id === ${$model}->user_id;
});
}
}
and then inside boot method
$this->defineAbilities(['ability1' => 'model1', 'ability2' => 'model2'], $gate);
You can define another function and call it within the anonymous function. This will allow you to have commonly-used code in one central location while still allowing any resource-specific logic.
Add this function to your AuthServiceProvider class:
public function userCheck(User $user, $target)
{
// do the user id check
$result = isset($target->user_id) && isset($user) && $user->id === $target->user_id;
return $result;
}
Your code, modified:
$gate->define('update-post', function ($user, $post) {
// call the function
$result = $this->userCheck($user, $post);
// do some kind of 'update-post' specific check
return $result/* && some_bool_statement*/;
});
I think you can use middlewares.
Simply make a admin middleware and use it in your routes and routes group.
And there is no security bug on your project (delete, create & ... actions) because Laravel has csrf token!
You can use before() function, also.
And then an important note:
if you don't define a correspond function on Policy class and call it $this->authorize($post) on a controller an unauthorized Action error will be thrown unless before()methodreturnstrue.
for example call $this->authorize on Dashboard\PostsController:
public function edit($id)
{
$post = Post::find($id)->first();
$this->authorize($post);
return view('dashboard.post')->with(compact('post'));
}
and if we defined a PostPolicy Class:
class PostPolicy
{
use HandlesAuthorization;
public function before($user, $ability)
{
return $user->is_admin;
}
}
If user be admin he/she can edit post because we returned true in before() method despite of have not a method with same name (as edit method in PostsController).
In fact Laravel will check for before method mthod on Policy Class. if before return'snull will check for correspond method with same name on controller method and if this method not found user cannot perform action.
Thank you laravel for DRY us!♥

Categories