Laravel 5: cookie set by custom Middleware not readable in Controller - cookies

I created a website with Laravel 5. In order to keep track of users that were referred to by some other website I use the following middleware that recognizes the referral by a request parameter and sets a cookie.
<?php
namespace App\Http\Middleware;
use Closure;
class AffiliateTrackingMiddleware {
public static $trackingCookieName = "refId";
public static $trackingURLParameter = "refId";
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
$response = $next($request);
if ($request->has(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingURLParameter)) {
return $response->withCookie(cookie()->forever(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingCookieName, $request->get(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingURLParameter)));
}
return $response;
}
}
In my controller I would like to retrieve the value of the cookie. I implemented the following function for that. For the case that there is no cookie available I also check if there is a request parameter set to identify the referrer.
protected function getAffiliateId($request = null) {
$affiliateCookieValue = Cookie::get(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingCookieName);
if (!empty($affiliateCookieValue)) {
return $affiliateCookieValue;
} else {
if ($request !== null) {
if (!empty($request->get(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingURLParameter))) {
$affiliateRequestValue = $request->get(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingURLParameter);
return $affiliateRequestValue;
} else {
$affiliateCookieValue = $request->cookie(\App\Http\Middleware\AffiliateTrackingMiddleware::$trackingCookieName);
return $affiliateCookieValue;
}
} else {
return "";
}
}
}
The strange thing now is that within the constructor of my controller I am able to retrieve the correct affiliate id by cookie. However, when I call the function getAffiliateId from within another function of my controller the cookie value remains empty.
Why is it not possible for me to retrieve the cookie value set up in the middleware from any function in my controller?
In my browser I can also see the cookie.
Thanks a lot in advance.
added: The code of the Http/Kernel.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel {
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* #var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\AfterMiddleware::class,
\App\Http\Middleware\AffiliateTrackingMiddleware::class,
];
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
// \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
}

Since you want to use your middleware inside of your web middleware, append your middleware at the end of web group middleware, like so:
'web' => [
...
\App\Http\Middleware\AffiliateTrackingMiddleware::class,
]
Also make sure that your cookies are not empty when the request hits your middleware

Related

Laravel Livewire database notification doesn't exist yet when broadcast notification received

I've worked on getting database and broadcast notifications to play nice for quite a while, and I've finally got it working and identified an issue I don't know how to solve.
The root cause seems to be that the notification from pusher is getting back to the app prior to when the database notification is available to select.
This results in my notification icon showing up on the front end, but the notifications array is empty:
public function getListeners()
{
return [
"echo-private:users.{$this->user->id},StatementCompleted" => 'notifyUser',
];
}
public function notifyUser(mixed $notification)
{
if (!empty($notification)) {
$this->showNotificationsBadge = true;
ray('notification!');
$this->refreshNotifications();
ray($this->notifications); <-- THIS IS EMPTY
}
}
public function refreshNotifications()
{
$this->notifications = $this->user->unreadNotifications()->get();
$this->notificationCount = $this->user->unreadNotifications()->count();
}
HOWEVER, if I add sleep(5) into the notifyUser() method like so:
public function notifyUser(mixed $notification)
{
if (!empty($notification)) {
$this->showNotificationsBadge = true;
ray('notification!');
sleep(5); <-- ADDED THIS
$this->refreshNotifications();
}
}
it results in the notifications array containing the notification and everything works fine. So my theory is that it's a timing issue.
How do I ensure my database notification exists before the pusher notification triggers my livewire front-end refresh?
EVENT/LISTENER/NOTIFICATION CLASSES:
Here's my event:
class StatementCompleted implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $statement;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Statement $statement)
{
$this->statement = $statement;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('users.' . $this->statement->uploadedBy->id);
}
}
and here's the event listener class:
class HandleStatementCompletedEvent implements ShouldQueue
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle($event)
{
Notification::send($event->statement->uploadedBy, new SendStatementCompletedNotification($event->statement));
}
}
and here's the notification class:
class SendStatementCompletedNotification extends Notification implements ShouldQueue, ShouldBroadcast
{
use Queueable;
public $statement;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Statement $statement)
{
$this->statement = $statement;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database', 'broadcast'];
}
/**
* Get the broadcastable representation of the notification.
*
* #param mixed $notifiable
* #return BroadcastMessage
*/
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'title' => 'Statement Processed',
'message' => "Your statement {$this->statement->original_file_name} has been processed.",
'user_id' => $this->statement->uploadedBy->id
]);
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toDatabase($notifiable)
{
return [
'title' => 'Statement Processed',
'message' => "Your statement {$this->statement->original_file_name} has been processed.",
'user_id' => $this->statement->uploadedBy->id
];
}
}

cakephp 3 undefined property cookie component unit test

I tried to test my component function through unit testing.
My component function below
public function userRole() {
$loginId = $this->Cookie->read('Admin.login_id');
$name = $this->Cookie->read('Admin.name');
$role = $this->Cookie->read('Admin.role');
if (empty($loginId) || empty($name)){
return false;
}
$adminsORM = TableRegistry::get('Admins');
$admin = $adminsORM->find('all', [
'conditions' => ['login_id' => $loginId, 'name' => $name, 'disable' => 0]
])->first();
return empty($admin)? false : $admin->role;
}
And my component testing function below
public $Acl;
public function setUp()
{
parent::setUp();
$registry = new ComponentRegistry();
$this ->Acl = new AclComponent($registry);
}
public function testUserRole()
{
// Test our adjust method with different parameter settings
$this->Cookie->write('Admin.login_id', 'demo12');
$this->Cookie->write('Admin.role', 1);
$this->Cookie->write('Admin.name', 'demo 12');
$output = $this->Acl->userRole();
$this->assertResponseOk();
}
composer testing code
vendor/bin/phpunit --filter testUserRole /d/xampp/htdocs/admin/admin/tests/TestCase/Controller/Component/AclComponentTest.php
error
Notice Error: Undefined property: App\Test\TestCase\Controller\Component\AclComponentTest::$Cookie in [D:\xampp\htdocs\admin\admin\tests\TestCase\Controller\Component\AclComponentTest.php, line 31]
As the error suggests, there is no $this->Cookie property in your unit test. I can only assume that $this->Cookie in your component refers to the Cookie component (which btw is deprecated as of CakePHP 3.5).
If you need to prepare cookies for a regular unit test, and not a controller/integration test (where you could to use the IntegrationTestCase::cookie(), IntegrationTestCase::cookieEncrypted(), IntegrationTestCase::assertResponseOk() methods), then you have to write the cookies directly to the request object, and make sure that you make it available to the component.
Check out the example in the Cookbook on how to test components, it should look something like this:
namespace App\Test\TestCase\Controller\Component;
use App\Controller\Component\MyComponent;
use Cake\Controller\Controller;
use Cake\Controller\ComponentRegistry;
use Cake\Http\ServerRequest;
use Cake\Http\Response;
use Cake\TestSuite\TestCase;
class MyComponentTest extends TestCase
{
public $component = null;
public $controller = null;
public function setUp()
{
parent::setUp();
$request = new ServerRequest();
$response = new Response();
$this->controller = $this->getMockBuilder('Cake\Controller\Controller')
->setConstructorArgs([$request, $response])
->setMethods(null)
->getMock();
$registry = new ComponentRegistry($this->controller);
$this->component = new MyComponent($registry);
}
// ...
}
You can then either define the cookies in the setUp() method, so that they are available in all tests, or you can define them individually per test. Also note that if you're working with encrypted cookies, you should use CookieCryptTrait::_encrypt() to encrypt the cookie data.
// ...
use Cake\Utility\CookieCryptTrait;
use Cake\Utility\Security;
protected function _getCookieEncryptionKey()
{
// the cookie component uses the salt by default
return Security::getSalt();
}
public function testUserRole()
{
$data = [
'login_id' => 'demo12',
'role' => 1,
'name' => 'demo 12'
];
// the cookie component uses `aes` by default
$cookie = $this->_encrypt($data, 'aes');
$request = new ServerRequest([
'cookies' => [
'Admin' => $cookie
]
]);
$this->controller->request = $request;
$output = $this->Acl->userRole();
$this->assertEquals('expected value', $output);
}
See also
Cookbook > Testing > Testing Components
API > \Cake\Utility\CookieCryptTrait
Based on the testing documentation, in order to set your cookies during your test cases, you need to use the function $this->cookieEncrypted('my_cookie', 'Some secret values'):
$this->cookieEncrypted('Admin.login_id', 'demo12');
$this->cookieEncrypted('Admin.role', 1);
$this->cookieEncrypted('Admin.name', 'demo 12');

how to test slim middleware (phpunit)

I created a Middleware wich should only redirect the user to an other Website (given in Request Url by the Parameter redirect)
class Middleware
{
public function __invoke($request, $response, $next)
{
// Call next middleware or app
$response = $next($request, $response);
$redirectUrl = //get redirect url
return $response->withStatus(200)->withHeader('Location', $redirectUrl);
}
}
I already testet this and the Redirect works fine. So I came to that Point to write Unit-Tests. I failed ... This was my attempt:
class MiddlewareTest extends \PHPUnit_Framework_TestCase
{
public $request = array(...); //inserted needed properties
public function testInvoke(String $url) {
$next = function () : bool
{
return true;
}; //empty function
$request['request']['scriptUri'] = "/parameterStuff&redirect=" . $url; //overwrite the Uri with provided Url
$redirect = new Middleware($request, array(), $next);
//just to test if result of response still empty
$iCount = count((array)$redirect);
$this->assertEquals(0, $iCount);
}
public function invokeProvider() : array
{
return array(
array('http://example.com')
);
}
}
This test is successful but ofc it shouldn't... The return of this function should be a valid response. I tested this in my Browser and echo the return. It has a value there and it's the correct response with the expected Header. The return Value I receive in my Unit-Test is an empty object.
I red the Slim Documentation about the response Object and it sais:
This method returns a copy of the Response object that has the new header value.
So I should definitely receive something from it. I also tried to return a copy of the response:
$copyresponse = response->withStatus(200)->withHeader('Location', $redirectUrl);
return $copyresponse;
This don't works as well. Any Idea what could cause my Problem and how to solve it?
(I want to test if the redirect url is set correctly in the response to ensure that the redirect would work)
You have to mock the request and check if the Location header is correctly set, its length is 1 and the status code is 200. I wrote some different middleware and I used this method.
class LocationTest extends \PHPUnit_Framework_TestCase
{
/**
* PSR7 request object.
*
* #var Psr\Http\Message\RequestInterface
*/
protected $request;
/**
* PSR7 response object.
*
* #var Psr\Http\Message\ResponseInterface
*/
protected $response;
protected $headers;
protected $serverParams;
protected $body;
/**
* Run before each test.
*/
public function setUp()
{
$uri = Uri::createFromString('https://example.com:443/foo/bar');
$this->headers = new Headers();
$this->headers->set('REMOTE_ADDR', '127.0.0.1');
$this->cookies = [];
$env = Environment::mock();
$this->serverParams = $env->all();
$this->body = new Body(fopen('php://temp', 'r+'));
$this->response = new Response();
$this->request = new Request('GET', $uri, $this->headers, $this->cookies, $this->serverParams, $this->body);
}
/**
* #dataProvider locationProvider
*/
public function testLocation($url)
{
$options = array(
'ip' => '192.*',
);
$mw = new RestrictRoute($options);
$next = function ($req, $res) {
return $res;
};
$uri = Uri::createFromString('https://example.com:443/foo/bar?redirect=' . $url);
$this->request = new Request('GET', $uri, $this->headers, $this->cookies, $this->serverParams, $this->body);
$redirect = $mw($this->request, $this->response, $next);
$location = $redirect->getHeader('Location');
$this->assertEquals($redirect->getStatusCode(), 200);
$this->assertEquals(count($location), 1);
$this->assertEquals($location[0], $url);
}
public function locationProvider(){
return [
['http://www.google.it'],
['http://stackoverflow.com/'],
];
}
}

Unit test Laravel middleware

I am trying to write unit tests for my middleware in Laravel. Does anyone know a tutorial, or have an example of this ?
I have been writing a lot of code, but there must be a better way to test the handle method.
Using Laravel 5.2, I am unit testing my middleware by passing it a request with input and a closure with assertions.
So I have a middleware class GetCommandFromSlack that parses the first word of the text field in my Post (the text from a Slack slash command) into a new field called command, then modifies the text field to not have that first word any more. It has one method with the following signature: public function handle(\Illuminate\Http\Request $request, Closure $next).
My Test case then looks like this:
use App\Http\Middleware\GetCommandFromSlack;
use Illuminate\Http\Request;
class CommandsFromSlackTest extends TestCase
{
public function testShouldKnowLiftCommand()
{
$request = new Illuminate\Http\Request();
$request->replace([
'text' => 'lift foo bar baz',
]);
$mw = new \App\Http\Middleware\GetCommandFromSlack;
$mw->handle($request,function($r) use ($after){
$this->assertEquals('lift', $r->input('command'));
$this->assertEquals('foo bar baz',$r->input('text'));
});
}
}
I hope that helps! I'll try to update this if I get more complicated middleware working.
To actually test the middleware class itself you can do:
public function testHandle()
{
$user = new User(['email'=>'...','name'=>'...']);
/**
* setting is_admin to 1 which means the is Admin middleware should
* let him pass, but oc depends on your handle() method
*/
$user->is_admin = 1;
$model = $this->app['config']['auth.model'];
/**
* assuming you use Eloquent for your User model
*/
$userProvider = new \Illuminate\Auth\EloquentUserProvider($this->app['hash'], $model);
$guard = new \Illuminate\Auth\Guard($userProvider, $this->app['session.store']);
$guard->setUser($user);
$request = new \Illuminate\Http\Request();
$middleware = new \YourApp\Http\Middleware\AuthenticateAdmin($guard);
$result = $middleware->handle($request, function(){ return 'can access';});
$this->assertEquals('can access',$result);
}
I thinking the best solution is just checking what happened after middleware. For example, the authentication middleware:
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate {
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest())
{
if ($request->ajax())
{
return response('Unauthorized.', 401);
}
else
{
return redirect()->guest('auth/login');
}
}
return $next($request);
}
}
And my test unit:
<?php
class AuthenticationTest extends TestCase {
public function testIAmLoggedIn()
{
// Login as someone
$user = new User(['name' => 'Admin']);
$this->be($user);
// Call as AJAX request.
$this->client->setServerParameter('HTTP_X-Requested-With', 'XMLHttpRequest');
$this->call('get', '/authpage');
$this->assertEquals(200, $response->getStatusCode());
}
}
I would do it in that way.
I was working on a localization Middleware that sets the app locale based on a URI segment, e.g. http://example.com/ar/foo should set the app local to Arabic. I basically mocked the Request object and tested as normal. Here is my test class:
use Illuminate\Http\Request;
use App\Http\Middleware\Localize;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class LocalizeMiddlewareTest extends TestCase
{
protected $request;
protected $localize;
public function setUp()
{
parent::setUp();
config(['locale' => 'en']);
config(['app.supported_locales' => ['en', 'ar']]);
$this->request = Mockery::mock(Request::class);
$this->localize = new Localize;
}
/** #test */
public function it_sets_the_app_locale_from_the_current_uri()
{
$this->request->shouldReceive('segment')->once()->andReturn('ar');
$this->localize->handle($this->request, function () {});
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_allows_designating_the_locale_uri_segment()
{
$this->request->shouldReceive('segment')->with(2)->once()->andReturn('ar');
$this->localize->handle($this->request, function () {}, 2);
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_throws_an_exception_if_locale_is_unsupported()
{
$this->request->shouldReceive('segment')->once()->andReturn('it');
$this->request->shouldReceive('url')->once()->andReturn('http://example.com/it/foo');
$this->setExpectedException(
Exception::class,
"Locale `it` in URL `http://example.com/it/foo` is not supported."
);
$this->localize->handle($this->request, function () {});
}
}
And here is my Middleware class:
namespace App\Http\Middleware;
use Closure;
class Localize
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param integer $localeUriSegment
* #return mixed
*/
public function handle($request, Closure $next, $localeUriSegment = 1)
{
$locale = $request->segment($localeUriSegment);
if (in_array($locale, config('app.supported_locales')))
{
app()->setLocale($locale);
}
else
{
abort(500, "Locale `{$locale}` in URL `".$request->url().'` is not supported.');
}
return $next($request);
}
}
Hope that helps :)

Unit Testing and FOSUserBundle

I'm writing unit tests for my Symfony 2 app which uses FOSUserBundle. Unlike this similar question:
FOSUserBundle Unit testing
however, I don't use HTTP authentication (only a login form) and I need to use actual user entities not fake in-memory ones.
Despite a lot of searching and trying, I simply can't get it to work and the entire process is so intransparent that I don't even know where to begin. Here's the code I have:
protected $em;
protected $client;
protected $testuser;
public function setUp() {
$kernel = static::createKernel();
$kernel->boot();
$this->em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
$this->em->beginTransaction();
$this->client = static::createClient();
$usermanager = $kernel->getContainer()->get('fos_user.user_manager');
$this->testuser = $usermanager->createUser();
$this->testuser->setUsername('test');
$this->testuser->setEmail('test#lemuria.org');
$this->testuser->setPlainPassword('test');
$usermanager->updateUser($this->testuser);
}
public function testLogin() {
$crawler = $this->client->request('GET', '/en/login');
$form = $crawler->selectButton('_submit')->form(array(
'_username' => 'test',
'_password' => 'test',
));
$this->client->submit($form);
$this->assertTrue($this->client->getResponse()->isRedirect(), 'should be redirected');
$this->assertTrue($this->client->getResponse()->isRedirect('http://localhost/en/account'), 'should be redirected to account page');
$crawler = $this->client->followRedirect();
and it fails on the 2nd assertion. As far as I can figure out, it redirects back to the login page.
I'm stuck and I don't even know where to start looking for a solution because it's apparently impossible to simply figure out WHY the login fails.
You could try adding echo $this->client->getResponse()->getContent() to show you the actual response where you could look for errors
Create an AbstractControllerTest and create an authorized client on setUp() as follow:
abstract class AbstractControllerTest extends WebTestCase
{
/**
* #var Client
*/
protected $client = null;
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
/**
* #return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$container = $client->getContainer();
$session = $container->get('session');
/** #var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** #var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $container->getParameter('fos_user.firewall_name');
$user = $userManager->findUserBy(array('username' => 'REPLACE_WITH_YOUR_TEST_USERNAME'));
$loginManager->loginUser($firewallName, $user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_' . $firewallName,
serialize($container->get('security.context')->getToken()));
$container->get('session')->save();
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
}
NOTE: Please, replace the username with your test username.
Then, extends the AbstractControllerTest and use the global $client to make requests as follow:
class ControllerTest extends AbstractControllerTest
{
public function testIndexAction()
{
$crawler = $this->client->request('GET', '/admin/');
$this->assertEquals(
Response::HTTP_OK,
$this->client->getResponse()->getStatusCode()
);
}
}
This method tested and works fine