Laravel Version 5.2
In my project, users with role_id = 4 has the admin role and can manage users.
I have defined the following ability in AuthServiceProvider:
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('can-manage-users', function ($user)
{
return $user->role_id == 4;
});
}
I have used this ability in the UserController __construct method as follows:
public function __construct()
{
$this->authorize('can-manage-users');
}
In ExampleTest, I have created two tests to check if the defined authorization works.
The first test for admin user who has role_id = 4. This test passes.
public function testAdminCanManageUsers()
{
$user = Auth::loginUsingId(1);
$this->actingAs($user)
->visit('users')
->assertResponseOk();
}
The second test is for another user who does not have role_id = 4. I have tried with response status 401 and 403. But the test is failing:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertResponseStatus(403);
}
First few lines of the failure message is given below:
A request to [http://localhost/users] failed. Received status code [403].
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:196
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:80
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:61
C:\wamp\www\laravel\blog\tests\ExampleTest.php:33
Caused by exception 'Illuminate\Auth\Access\AuthorizationException'
with message 'This action is unauthorized.' in
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Auth\Access\HandlesAuthorization.php:28
I have also tried to use 'see' method as follows:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->see('This action is unauthorized.');
}
But it's failing too. What am I doing wrong? How can I make the test pass?
The mistake is calling the visit method. The visit method is in the InteractsWithPages trait. This method calls the makeRequest method which in turn calls assertPageLoaded method. This method gets the status code returned and if it gets code other than 200, it catches a PHPUnitException and throws an HttpException with the message
"A request to [{$uri}] failed. Received status code [{$status}]."
This is why the test was failing with the above message.
The test can be successfully passed by using get method instead of visit method. For example:
public function testNonAdminCannotManageUsers()
{
$user = App\User::where('role_id', '<>', 4)->first();
$this->actingAs($user)
->get('users')
->assertResponseStatus(403);
}
This test will pass and confirm that a non admin user cannot access the url.
Since the Auth middleware redirects to a login route when unauthenticated by default you could also perform the following test:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertRedirect('login');
}
Since at least Laravel 5.4, you'll want to use the assertStatus(403) method.
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertStatus(403);
}
Related
I created my feature test;
ProfilesControllerTest.php
<?php
namespace Tests\Feature;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class ProfileControllerTest extends TestCase
{
use RefreshDatabase;
/**
* A basic feature test example.
*
* #return void
*/
public function test_the_profile_page_is_rendered()
{
// First The user is created
$user = User::factory()->create();
//act as user
$this->actingAs($user);
// Then we want to make sure a profile page is created
$response = $this->get('/profile/{user}');
//
$response->assertStatus(200);
}
}
web.php
Route::get('/profile/{user}', 'ProfilesController#index')->name('profiles.show');
But it keeps returning an error. I Suspect it is because of the profile link, however I am unsure of how to show it. I have attempted a few variations and i have not managed to get it to work.
I realised factory was not working so instead I tried this;
ProfilesControllerTest.php
public function test_the_profile_page_is_rendered()
{
// First The user is created
$user = User::make([
'name' => 'John Doe',
'username' => 'johnnyd',
'email' => 'johndoe#email.com'
]);
//act as user
$this->actingAs($user);
// Then we want to make sure a profile page is created
$response = $this->get('/profile/{$user}');
$response->assertStatus(200);
}
And I kept getting the error:
Error
php artisan test
PASS Tests\Unit\ExampleTest
✓ basic test
PASS Tests\Unit\UserTest
✓ login form
✓ user duplication
PASS Tests\Feature\ExampleTest
✓ basic test
FAIL Tests\Feature\ProfileControllerTest
✕ the profile page is rendered
Tests: 1 failed, 4 passed, 1 pending
Expected status code 200 but received 404. Failed asserting that 200 is identical to 404.
at tests/Feature/ProfileControllerTest.php:34
30| // Then we want to make sure a profile page is created
31| $response = $this->get('/profile/{$user');
32|
33| //
> 34| $response->assertStatus(200);
35| }
36| }
37|
Profile controller for index was written as follows:
ProfilesController.php
class ProfilesController extends Controller
{
public function index(User $user)
{
$postCount = Cache::remember(
'count.posts.' . $user->id,
now()->addSeconds(30),
function () use ($user) {
return $user->posts->count();
}
);
return view('profiles.index', compact('user', 'postCount'));
}
}
Your first test isn't working because you're attempting to access the wrong URL. You're attempting to go to http://localhost/profile/{user}. That URL is not correct, as there is no user with an id of "{user}". The URL you want to access is http://localhost/profile/1, to see the profile of the user with id 1.
To fix the first test, fix the URL:
// bad
// $response = $this->get('/profile/{user}');
// good
$response = $this->get('/profile/'.$user->id);
Your second test is failing for two reasons:
User::make() will make a new instance of the User model, but it will not persist anything to the database. Since the User won't exist in the database, it does not have a profile URL you can visit.
Again, as in the first test, the profile URL you're trying to visit is wrong.
So, go back to the first test, correct the URL, and you should be good.
I am working with Laravel 8.25. I have a Service class that has the following method:
public function getUsersBusinessManagers()
{
$user = Auth::user()->id;
return $user->businessManagers;
}
My test for this method is:
public function testCorrectBusinessManagersRetrievedForUser()
{
$user = User::factory()
->hasAttached(
BusinessManager::factory()->count(1),
['user_fb_bm_id' => 'test']
)
->create();
$businessManagerService = new BusinessManagerService();
$usersBusinessManagers = $businessManagerService->getUsersBusinessManagers();
$this->actingAs($user)->assertEquals($user->businessManagers()->first()->id, $usersBusinessManagers->first()->id);
}
When running the test, how can I get Auth:user() in my service class to return the user I create in my test? Currently Auth:user() is null when I run the test.
Appreciate any help or guidance.
actingAs() is used for the Laravel test web server, i would assume you could just login in the user.
Auth::login($user);
So I have an Laravel app, but I have overridden the default sendEmailVerificationNotification function in my App\User.php. Because I didn't want the default email thing.
Now, when I register, I get an email and activation etc... That all works perfectly. However, when I click the link, I get a 500 error... So I go and look into the logs and see the follwoing error:
Class 'App\Http\Controllers\Auth\Verified' not found
Now, indeed, that class doesn't exist, because I have no idea what I should do in that class...
In my User.php, the verify method is the following;
public function verify(Request $request): Response
{
if ($request->route('id') != $request->user()->getKey()) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
toastr()->success('Uw email is geverifiëerd', 'Gelukt!', ['timeOut' => 5000]);
}
return redirect($this->redirectPath())->with('verified', true);
}
The full error is this:
[2019-04-14 11:57:29] staging.ERROR: Class
'App\Http\Controllers\Auth\Verified' not found
{"userId":3,"exception":"[object]
(Symfony\Component\Debug\Exception\FatalThrowableError(code: 0):
Class 'App\Http\Controllers\Auth\Verified' not found at
/var/www/rpr/releases/20190414113903/app/Http/Controllers/Auth/VerificationController.php:60)
Line 60 in VerficationController.php is the } of the if-statement with hasVerifiedEmail.
Can someone please explain how I can just verify the user and give a notification that the account has been verified?
You must use the Auth facade. Add this line to your controller:
use Illuminate\Support\Facades\Auth;
You forgot to add Verified class to your use, then add:
use Illuminate\Auth\Events\Verified;
I am using facebook as the login of my application. After the user successfully logs in, they are redirected to my user homepage. But when I try to refresh the page after log in it throws an error:
Laravel \ Socialite \ Two \ InvalidStateException
No message
This is the code displayed:
public function user()
{
if ($this->hasInvalidState()) {
throw new InvalidStateException; // this line is highlighted
}
$response = $this->getAccessTokenResponse($this->getCode());
$user = $this->mapUserToObject($this->getUserByToken(
$token = Arr::get($response, 'access_token')
));
return $user->setToken($token)
->setRefreshToken(Arr::get($response, 'refresh_token'))
->setExpiresIn(Arr::get($response, 'expires_in'));
}
This is my Controller code:
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Socialite;
class SocialAccountController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Redirect the user to the SNS authentication page.
*
* #return Response
*/
public function redirectToProvider($provider)
{
if ($provider !== 'facebook') {
return abort(404);
}
return Socialite::with($provider)->redirect();
}
public function handleProviderCallback(\App\Models\User $accountService, $provider)
{
try {
$user = Socialite::with($provider)->user();
$create['name'] = $user->getName();
$create['email'] = $user->getEmail();
$create['facebook_id'] = $user->getId();
$user = $accountService->addNew($create);
return view ('user.home')->withDetails($user)->withService($provider);
} catch(Exception $e){
return redirect('/login');
}
}
}
I have found some answers but is not effective on my side. I'm stuck on this for 2 days now. How can I resolve this?
When you get the callback from an OAuth provider (in this case, Facebook) store the data you need into your database and then redirect the user to an account page, or another page that confirms the link was successful.
That callback URL works specifically with credentials that are provided by the OAuth provider. After it is hit the first time those credentials expire and refreshing the page causes the error. By forcefully redirecting the user to another page then they won't be able to refresh and cause the same issue.
Try to refresh the client_secret and run php artisan cache:clear and
composer dump-autoload. It works fine for me.
I am using Laravel 5.1 and I am trying to test my controllers.
I have several roles for my users and policies defined for different actions. Firstly, each of the requests needs to be made by an authenticated user, so running a test with no user returns a 401 Unauthorized, as expected.
But when I want to test the functionality for authorized users, I still get the 401 Unauthorized status code.
It may be worth mentioning that I use basic stateless HTTP authentication on these controllers.
I have tried the following:
public function testViewAllUsersAsAdmin()
{
$user = UserRepositoryTest::createTestAdmin();
Auth::login($user);
$response = $this->call('GET', route('users.index'));
$this->assertEquals($response->getStatusCode(), Response::HTTP_OK);
}
and
public function testViewAllUsersAsAdmin()
{
$user = UserRepositoryTest::createTestAdmin();
$response = $this->actingAs($user)
->call('GET', route('users.index'));
$this->assertEquals($response->getStatusCode(), Response::HTTP_OK);
}
and also this (in case there was anything wrong with my new user, which there shouldn't be)
public function testViewAllUsersAsAdmin()
{
$user = User::find(1);
$response = $this->actingAs($user)
->call('GET', route('users.index'));
$this->assertEquals($response->getStatusCode(), Response::HTTP_OK);
}
but in every case I get a 401 response code so my tests fail.
I can access the routes fine using postman when logging in as a dummy user.
I am running out of ideas, so any help would be appreciated.
You need to add Session::start() in the setUp function or in the beginning of the function which user need to log in.
public function setUp()
{
parent::setUp();
Session::start();
}
or
public function testViewAllUsersAsAdmin()
{
Session::start();
$user = UserRepositoryTest::createTestAdmin();
Auth::login($user);
$response = $this->call('GET', route('users.index'));
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
}
Through some experimentation, I found that the problem lay inside my authentication middleware. Since I want the API to be stateless, the authentication looks like this:
public function handle($request, Closure $next)
{
return Auth::onceBasic() ?: $next($request);
}
And apparently, it's not possible to authenticate a user the way I was doing it.
My solution was simply to disable the middleware, using the WithoutMiddleware trait or $this->withoutMiddleware() at the beginning of each test.