Laravel & phpunit set expected http error code - php

I'm writing some tests. Here is my test:
/** #test */
public function a_normal_user_cannot_access_the_admin_panel()
{
// Note: This is a regular user, not an admin
$user = factory(User::class)->create();
$this->actingAs($user);
$this->visit('/admin');
// ????
}
In my MustBeAdministrator.php middleware:
public function handle($request, Closure $next)
{
$user = $request->user();
if ($user && $user->isAdmin) {
return $next($request);
}
abort(403);
}
When i visit /admin, the middleware aborts with a 403 error. How can i assert with phpunit that an http error was thrown? I know about $this->setExpectedException(), but i can't get it to work with http error. Am i doing this wrong?
NOTE: I'm very new to phpunit and also exceptions, so sorry if this is a stupid question. Here is the repo for this project if you need any other files, or you can just ask.

$user = factory(User::class)->create();
$this->actingAs($user);
$this->setExpectedException('Symfony\Component\HttpKernel\Exception\HttpException');
$this->get('/admin');
throw $this->response->exception;
Found this article. Adding the setExpectedException line, the throw line, and changing visit to get seemed to solve my problem

if you want to get the full response object, you may use the call method
/** #test */
public function a_normal_user_cannot_access_the_admin_panel()
{
// Note: This is a regular user, not an admin
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->call('GET', '/admin');
$this->assert(403, $response->status());
}

Related

Laravel 'can' middleware return error 500 "This action is unauthorized"

i'm trying to create a website based on laravel framework. I'm stuck in permission control with Policy. This is my code:
+ Policies Register in App\Providers\AuthServiceProvider:
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
User::class => UserPolicy::class,
Category::class => CategoryPolicy::class
];
public function boot()
{
$this->registerPolicies();
try {
Permission::all()->each(function($permission) {
Gate::define($permission->name, function ($user) use($permission) {
return $user->hasPermission($permission->name);
});
});
} catch (\Exception $e) {
Log::notice('Unable to register gates. Either no database connection or no permissions table exists.');
}
}
}
User Policy in App\Policies\UserPolicy
class UserPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true;
}
public function view(User $user, User $target_user)
{
return $user->id === $target_user->id;
}
}
Api Route in routes/api.php
Route::get('/users', 'Api\UserController#getUsers')->middleware('can:view-users');
Route::get('/users/{user_id}', 'Api\UserController#getUser')->middleware('can:view-users');
Route::put('/users/{user_id}', 'Api\UserController#updateUser')->middleware('can:edit-users');
User Controller in App\Http\Controllers\Api\UserController
public function getUsers(UserRequest $request) {
$users = $this->userRepository->getAll();
$this->authorize('view', $user);
return response($users, 200);
}
public function getUser(UserRequest $request, $user_id) {
$user = $this->userRepository->find($user_id);
$this->authorize('view', $user);
return response($user, 200);
}
When I try to get data by using 2 url above, it returned error 500 Internal Server Error even when user is authenticated :
{
"error": "This action is unauthorized."
}
I tried to log result and found that error come from middleware can or Illuminate\Auth\Middleware\Authorize.
Code line:
$this->gate->authorize($ability, $this->getGateArguments($request, $models)); in handle() function throw error above.
After hours searching, I could not figure out solution.
Anyone can point out my mistake?
Solved. After hours reading source code in library, I figured out problem. I used api guard for my app without passport module. Thus, variable $userResolver in Illuminate\Auth\Access\Gate class could not be set to authenticated user and got null value. To solve this problem, I created my own Authorize middleware instead of laravel middleware Authorize and use Auth::guard('api')->user() to get authenticated user.

Laravel 5: update Controller in Middleware

Is there any way to access (modify) $request "protected proprieties" in the Middleware, to modify requested Controller:
public function handle($request, Closure $next)
{
// change $request parameter
// $request->server->parameters->REQUEST_URI = "something else";
return $next($request);
}
I want to override requested Controller if Cache is valid for the request,
thanks,
You can change the page in the middleware by returning a redirect.
public function handle($request, Closure $next)
{
// change $request parameter
// $request->server->parameters->REQUEST_URI = "something else";
if ($request->something === 'anything')
return redirect()->to("/something-else");
return $next($request);
}
Update:
If you do not wish for the url to update, you could invoke the controller directly using:
app(\App\Http\Controllers\MyController::class)->getMethod();
Where you update the Controller and the method to the ones you need.
However I would not recommend this.

How to bind user object to request in a middleware

i'm writing an application in Laravel Spark 1.0 (Laravel 5.2). I wrote a custom middleware for agent (api) authentication. This is the code:
<?php
namespace App\Http\Middleware;
use App\Agent;
use Closure;
use Illuminate\Http\Request;
class AgentAuth
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if( isset($request->token) && !empty($request->token) )
{
$agent = Agent::where('token', '=', $request->token)->first();
if( $agent != NULL )
{
$team = $agent->Team()->first();
$user = $team->User()->first();
$request->merge(['team' => $team ]);
$request->merge(['user' => $user ]);
return $next($request);
}
else {
return response('Unauthorized 2.', 401);
}
}
else {
return response('Unauthorized 1.', 401);
}
}
}
In the default laravel authentication the user object is injected in the request (see laravel docs): https://laravel.com/docs/5.2/authentication#retrieving-the-authenticated-user
So you can retrieve the user using:
$request->user();
Spark obviously use this method to check if user subscription is valid (laravel\spark\src\Http\Middleware\VerifyUserIsSubscribed):
if ($this->subscribed($request->user(), $subscription, $plan, func_num_args() === 2)) {
return $next($request);
}
And it's not working because, with my middleware, you can retrieve the user using: $request->user; but not with the laravel defaults $request->user();
How should i inject the user object into the request?
Thank you in advance
EDIT:
Laravel in the service provider (Illuminate\Auth\AuthServiceProvider#registerRequestRebindHandler)
Use this code to bind object user to the request:
/**
* Register a resolver for the authenticated user.
*
* #return void
*/
protected function registerRequestRebindHandler()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function ($guard = null) use ($app) {
return call_user_func($app['auth']->userResolver(), $guard);
});
});
}
I tried to insert this code, with the appropriate correction, in the middleware but i can't figure out how to make it work.
I don't have a copy of Spark to try this & ensure what I'm doing is correct for you, but I think this will help:
1) An assumption - I believe you are saying that yes, this line will get you the user you want:
$user = $team->User()->first();
and you merely want to bind it to the request so that you can access this user later in your app via:
$request->user()
2) If this is true, then all I did was simplify the code you provided to add:
$request->merge(['user' => $user ]);
//add this
$request->setUserResolver(function () use ($user) {
return $user;
});
// if you dump() you can now see the $request has it
dump($request->user());
return $next($request);
I also $request->user() in the route closure, and it is there.
The app rebinding was a little strange to me, and didn't seem necessary. I'm not sure that anything would really need this for what you are doing.
You could use the auth system if that model implements the right interface, to log them in for the request.
Auth uses a rebinder to assign the userResolver on request. (So you get $request->user() from it). Check Illuminate\Auth\AuthServiceProvider#registerRequestRebindHandler to see how its setting that resolver.
$request->setUserResolver(....)
This is a very useful question. I was having trouble with the selected solution though. In my middleware I could successfully see $request->user(), however it was failing when using gates, namely in the Access/Gate class:
protected function raw($ability, $arguments = [])
{
if (! $user = $this->resolveUser()) {
return false;
}
// ...
This function is always returning false :/
So I did it as suggested here (http://laravel-recipes.com/recipes/230/setting-the-currently-authenticated-user), namely:
$usr = new User();
$usr->setAttribute('id', $request->user_id);
Auth::setUser($usr);
And it appears to be working without using setUserResolver().
Thanks
If you have the user ID you can easily authenticate the user with \Illuminate\Support\Facades\Auth::onceUsingId($user_id)
This updates the $request object. For example:
public function test(Request $request)
{
Auth::onceUsingId(19);
$next = new \App\Http\Controllers\OtherController();
return $next->otherMethod($request);
}

Slim 3 Middleware Redirect

I want to check if a user is logged in. Therefor I have an Class witch returns true or false. Now I want a middleware which checks if the user is logged in.
$app->get('/login', '\Controller\AccountController:loginGet')->add(Auth::class)->setName('login');
$app->post('/login', '\Controller\AccountController:loginPost')->add(Auth::class);
Auth Class
class Auth {
protected $ci;
private $account;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
$this->account = new \Account($this->ci);
}
public function __invoke($request, \Slim\Http\Response $response, $next) {
if($this->account->login_check()) {
$response = $next($request, $response);
return $response;
} else {
//Redirect to Homepage
}
}
}
So when the user is logged in the page will render correctly. But when the user is not autoriesed I want to redirect to the homepage. But how?!
$response->withRedirect($router->pathFor('home');
This doesn't work!
You need to return the response. Don't forget that the request and response objects are immutable.
return $response = $response->withRedirect(...);
I have a similar auth middleware and this is how I do it which also adds a 403 (unauthorized) header.
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);
Building off of tflight's answer, you will need to do the following to make everything work as intended. I tried to submit this as a revision, given that the code provided in tflight's answer would not work on the framework out of the box, but it was declined, so providing it in a separate answer:
You will need the following addition to your middleware:
protected $router;
public function __construct($router)
{
$this->router = $router;
}
Additionally, when declaring the middleware, you would need to add the following the constructor:
$app->getContainer()->get('router')
Something similar to:
$app->add(new YourMiddleware($app->getContainer()->get('router')));
Without these changes, the solution will not work and you will get an error that $this->router does not exist.
With those changes in place you can then utilize the code provided by tflight
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);
make basic Middleware and inject $container into it so all your middleware can extends it.
Class Middleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __get($property)
{
if (isset($this->container->{$property})) {
return $this->container->{$property};
}
// error
}
}
make sure your Auth middleware on the same folder with basic middleware or you can use namespacing.
class Auth extends Middleware
{
public function __invoke($request, $response, $next)
{
if (!$this->account->login_check()) {
return $response->withRedirect($this->router->pathFor('home'));
}
return $next($request, $response);
}
}
Use:
http_response_code(303);
header('Location: ' . $url);
exit;

How to change default redirect URL of Laravel 5 Auth filter?

By default if I am not logged and I try visit this in browser:
http://localhost:8000/home
It redirect me to http://localhost:8000/auth/login
How can I change to redirect me to http://localhost:8000/login
I wanted to do the same thing in Laravel 5.5. Handling authentication has moved to Illuminate\Auth\Middleware\Authenticate which throws an Illuminate\Auth\AuthenticationException.
That exception is handled in Illuminate\Foundation\Exceptions\Hander.php, but you don't want to change the original vendor files, so you can overwrite it with your own project files by adding it to App\Exceptions\Handler.php.
To do this, add the following to the top of the Handler class in App\Exceptions\Handler.php:
use Illuminate\Auth\AuthenticationException;
And then add the following method, editing as necessary:
/**
* Convert an authentication exception into an unauthenticated response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login'); //<----- Change this
}
Just change return redirect()->guest('login'); to return redirect()->guest(route('auth.login')); or anything else.
I wanted to write this down because it took me more than 5 minutes to figure it out. Please drop me a line if you happened to find this in the docs because I couldn't.
Just to extend #ultimate's answer:
You need to modify App\Http\Middleware\Authenticate::handle() method and change auth/login to /login.
Than you need to add $loginPath property to your \App\Http\Controllers\Auth\AuthController class. Why? See Laravel source.
In result you'll have this in your middleware:
namespace App\Http\Middleware;
class Authenticate {
/**
* 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('/login'); // <--- note this
}
}
return $next($request);
}
}
And this in your AuthController:
namespace App\Http\Controllers\Auth;
class AuthController extends Controller
{
protected $loginPath = '/login'; // <--- note this
// ... other properties, constructor, traits, etc
}
This is Laravel 5.4 Solution:
There is a new unauthenticated() method in app/Exceptions/Handler.php which handles unauthenticated users and redirects to login path.
So change
return redirect()->guest('login');
to
return redirect()->guest('auth/login');
Authentication checks are made using middleware in Laravel 5.
And the middleware for auth is App\Http\Middleware\Authenticate.
So, you can change it in handle method of the middleware.
In Laravel 5.6, go to app/Exceptions folder and open the Handler.php, add a new method that overrides the unauthenticated method like so:
protected function unauthenticated($request, AuthenticationException $exception)
{
if($request->ajax())
{
return response([
"message" => "Unauthenticated.",
"data" => [],
],401);
}
return redirect()->to('/');
}
This method is triggered when you access a protected route using the built-in "auth" middleware. Now you will have full control where to redirect or the response sent.
EDIT: On Laravel 5.1, simply add protected $redirectPath = '/url/you/want'; to AuthController would do the trick.
REFER : http://laravel.com/docs/5.1/authentication#included-authenticating
On Laravel 5.1, it is completely moved to another middleware named RedirectIfAuthenticated.php under App\Http\Middleware
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
return redirect('/'); //change this part to anywhere you wish to be redirected to
}
return $next($request);
}
Hope it helps.
could you please outputs php artisan route:list please
You are right you can set the following attributes:
protected $loginPath = 'xxx';
protected $redirectPath = 'xxx';
protected $redirectAfterLogout = 'xxx';
Set this attribute to you AuthController.php
Since your other question was marked as duplicate..I will try to answer it here..
First you need to change your route like
<?php
Route::get(config('constants.cms_path') . '/login', [
'as' => 'login',
'uses' => 'Auth\AuthController#getLogin'
]);
In your blade..make sure you use named route in the Login url link like
{{ route('login') }}
In Middleware/Authenticate.php change the redirect guest to
return redirect()->guest(config('constants.cms_path') . '/login');
To change the redirection after the login, you only have to go to app/Http/Controllers/Auth/LoginController.php and add that inside the class LoginController:
protected $redirectTo = '/redirect-url-here';
Same for redirection after a new users register, but in that case, on AuthController.php
For Laravel 5.4 You can set protected $redirectTo = '/'; in LoginController.php FILE.
Or in RegistersUsers.php file you can
protected function registered(Request $request, $user)
{
return redirect('tosomeRoute');
//Note: This code will run when
//The user has been registered
}

Categories