I'm trying to hide a button and show it only to the admin, I think i writed the code well but still can't get the result i want, i can't see the button with the admin account neither with a normal user account
The FolderPolicy code
public function create(User $user)
{
if($user->is_admin)
return true;
}
the view
#can('create')
<a class="btn btn-primary" href="{{ route('newdoss') }}">New Folder</a>
#endcan
the AuthServiceProvider file
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Folder' => 'App\Policies\FolderPolicy',
];
i know there is other ways to get the result i want by checking direclty if the authenticated user is an admin, but i want to figure out why this one is not working. Thanks.
You need to specify which resource, or model, you are trying to create. With create, you can pass the name of the class. For updates and deletes, you can pass the instance of the class.
#can('create', \App\Folder::class)
You also need to return false when the policy fails. You can simplify your create method to:
public function create(User $user)
{
return (bool) $user->is_admin
}
Related
I want to make sure that the current user is able to edit the users credentials so I made the following UserPolicy:
class UserPolicy
{
use HandlesAuthorization;
public function update(User $user, User $model)
{
return true;
//return $user->is($model);
}
}
I even registered the policiy inside AppServiceProvider:
protected $policies = [
User::class => UserPolicy::class
];
Now I try to add the following middleware to the update-route in web.php: "->middleware('can:update,user');" like this:
Route::patch('/profiles/{user}',function (){
dd('It works');
})->middleware('can:update,user');
But I keep getting the following error:
Error Class '2' not found
Where 2 is the user-id who we try to patch. If I was logged in with user-id 1 that will be the class not found. I don't understand why. I followed the documentation on Laravel website (https://laravel.com/docs/8.x/authorization#via-middleware).
I have also tried to set {user} to {user:id} -> Same result
I have tried adding the id on "can" like this: can:update,user:id -> Gives 403 not authorized
The edit.blade.php has the following:
<form action="/profiles/{{ auth()->user()->id }}" method="POST">
#csrf
#method('PATCH')
...INPUTS...
</form>
I have of course tried running: "php artisan optimize" with no effect
What am I missing here? What's wrong?
EDIT:
I now tried the same thing with a Gate instead. I put the following inside AppServiceProvider.php:
public function boot()
{
Gate::define('edit-user', function(User $currentUser, User $user){
return true;
//return $currentUser->id === $user->id;
});
}
And the following middleware inside web.php:
Route::patch('/profiles/{user}',function (){
dd('It works');
})->middleware('can:edit-user,user');
And it gives me the exact same error: Class 2 not found
I even tried to pass the full models path like this:
Route::patch('/profiles/{user}',function (){
dd('It works');
})->middleware('can:edit-user,App\Models\User');
And it gives me the following error:
Argument 2 passed to
App\Providers\AppServiceProvider::App\Providers{closure}() must be an
instance of App\Models\User, string given, called in
/var/www/vhosts/domain.com/httpdocs/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php
on line 474
I would think it is due to not using model binding, you are passing the id where it expects an user model. Check if this version works.
Route::patch('/profiles/{user}',function (User $user) {
dd('It works');
})->middleware('can:edit-user,user');
I have tried to used easyAdmin3 for making an admin account quickly, but how do you make a proper impersonate user action ?
I have tried a lot of things but the best option are made custom action so this link appear in page but it's don't works properly...
Impersonate works but on only page linked in url (impersonate has stopped if page change) and User don't change in Symfony Toolbar...
My custom Action :
public function configureActions(Actions $actions): Actions
{
$impersonate = Action::new('impersonate', 'Impersonate')
->linkToRoute('web_account_index', function (User $entity) {
return [
'id' => $entity->getId(),
'?_switch_user' => $entity->getEmail()
];
})
;
return parent::configureActions($actions)
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $impersonate)
;
}
Result :
Dashboard link for each user
After click on impersonate, I have this url :
https://blog-community.wip/account/7?eaContext=37a8719&?_switch_user=user7#user.com
Content are ok (page account for user 7) but Symfony Profiler show User admin instead of impersonated User :
Symfony profiler user logged
Change page exit impersonate...
Real Symfony impersonate keep impersonation even if page changes because profiler user logged are different Symfony profiler user logged with impersonate directly in url
documentation not refer this functionality, EasyAdmin's Github issues too ans this website too.
Thanks for help
I am not a big fan of using hardcoded rules, so injected the UrlGenerator to my CrudController:
$impersonate = Action::new('impersonate', 'Impersonate')
->linkToUrl(function (User $user): string {
return $this->urlGenerator->generate(
Routes::DASHBOARD,
['_switch_user' => $user->getEmail()],
UrlGeneratorInterface::ABSOLUTE_URL
);
});
Solved !
EasyAdmin add automatically some parameters in url so "?" are already here but I added it too in my custom action...
Example :
https://blog-community.wip/account/7?eaContext=37a8719&?_switch_user=user7#user.com
public function configureActions(Actions $actions): Actions
{
$impersonate = Action::new('impersonate', 'Impersonate')
->linkToRoute('web_account_index', function (User $entity) {
return [
'id' => $entity->getId(),
'_switch_user' => $entity->getEmail()
// removed ? before _switch_user
];
})
;
return parent::configureActions($actions)
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $impersonate)
;
}
For EasyAdmin 3.2.x the prior solution stopped working. This is working for me now:
public function configureActions(Actions $actions): Actions
{
$impersonate = Action::new('impersonate', false, 'fa fa-fw fa-user-lock')
//changed from linkToRoute to linkToUrl. note that linkToUrl has only one parameter.
//"admin/.. can be adjusted to another URL"
->linkToUrl(function (User $entity) {
return 'admin/?_switch_user='.$entity->getUsername();
})
;
$actions = parent::configureActions($actions);
$actions->add(Crud::PAGE_INDEX, $impersonate);
return $actions;
}
I see the very same problem here: Laravel policies : code change is ignored. Is there any policy cache to clear? and here: Laravel Policy bug
I'm writing a new policy, the easiest one, over mode User to check if the logged user is the same user in the database so he can edit his profile, so...
I create the policy file:
> php artisan make:policy UserPolicy
I register the policy in AuthServiceProvider.php:
...
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
];
...
In UserPolicy.php I create the edit function:
public function edit(User $authUser, User $user) {
return $authUser->id === $user->id;
}
In UserController.php I have edit:
public function edit($id)
{
//
$user = User::findOrFail($id);
$this->authorize($user);
return view('user.edit', compact('user'));
}
See somwthing wrong? Me neither, because it worked... the first time. Then I wanted to change the policy, the User model has a level attribute, 1 for normal users, 5 for admins, 99 for superuser and so on. So I wanted that the admins or superuser would be able to change the user data, so I rewrote the UserPolicy.php's editfunction as:
public function edit(User $authUser, User $user) {
return ($authUser->id === $user->id) || ($user->level > 1);
}
Of course I made a mistake here, I should've checked for $authUser and nor $user. When I checked in the browsser, function returned false, and server gave me a 403
This action is unauthorized., which is okay. Now the wierd thing. I correct the fuction:
public function edit(User $authUser, User $user) {
return ($authUser->id === $user->id) || ($authUser->level > 1);
}
it returns 403...
public function edit(User $authUser, User $user) {
return true;
}
It returns 403...
I delete the function from the file... It returns 403...
I delete the register from AuthServiceProvider... Ir returns 403...
No, I'm not using Gates, or some other thing, the Laravel app is almost virgin. I have had this problem in the past, that came out of the blue, and went the same way as it came. I have no idea where to look for, what to look for... I thought that would be some interaction that I didn't grasped, so I wanted to start with the policies from the beggining of this project.
EDIT::::::::::::::::::::::::::::
This is even more absurd... If I check using can() in tinker, this /#&)"# thing does what it is supposed to do:
> php artisan tinker
>>> $user = App\User::find(1)
>>> $user->can('edit', $user)
true
>>> $user2 = App\User::find(2)
>>> $user->can('edit', $user2)
false
So, problem is here????
$this->authorize($user);
EDIT 2 ::::::::::::: SOLVED ::::::::::::::::::::::
I swear this used to work as I posted above (at least it used to work in 5). I had to change
$this->authorize($user);
for
$this->authorize('edit', $user);
Solution came from this article
I swear this used to work as I posted above (at least it used to work in 5). I had to change
$this->authorize($user);
for
$this->authorize('edit', $user);
Solution came from this article
So, I have a page with a button on it with the value "Create". When I click that Create button, without filling out any of the fields, it validates the form and displays error messages on the same page. When I do that in the browser, it works fine, but when I do it with phpunit, it has unexpected results and I do not know why.
Here is my integration test:
public function testCreateValidation()
{
$this->visit(route('patients.indexes.create', $this->patient->id));
$this->press('Create');
$this->seePageIs(route('patients.indexes.create', $this->patient->id));
}
And this is the result:
There was 1 failure:
1) Tests\Integration\IndexControllerTest::testCreateValidation
Did not land on expected page [http://localhost/patients/69/indexes/create].
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'http://localhost/patients/69/indexes/create'
+'http://localhost/patients'
/vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:141
/vagrant/tests/Integration/IndexControllerTest.php:51
I don't understand why it is being redirected to the patients page.
Here is the Laravel create method that is being tested:
public function create($id)
{
$index = $this->indexes->newInstance();
$patient = $this->patients->findOrFail($id);
return view('patient.index.create', ['index' => $index, 'patient' => $patient]);
}
And here is the relevant section of the create view:
<?= Form::open(['route' => array('patients.indexes.store', $patient->id), 'class' => 'form-horizontal']) ?>
#include('patient.index._form')
<?= Form::submit('Create', ['class' => 'btn btn-primary']) ?>
<?= Form::close() ?>
And finally the store method that it is being sent to:
public function store(IndexRequest $request, $id)
{
$index = $this->indexes->newInstance();
$index->fill($request->all());
$index->patient_id = $id;
$index->save();
$patient = $index->patient;
return redirect()->route('patients.edit', $patient);
}
I am also using a FormRequest to validate the input:
public function rules()
{
return [
'index_title' => 'required',
'index_description' => 'required',
];
}
So essentially, since it is failing the validation in the IndexRequest, the IndexRequest should kick it back to the patients.indexes.create view and display errors. But for some reason it's being kicked to the patients page (this ONLY happens in the test, if I try it out by manually clicking the Create button in the browser, it works as expected)
I've had this issue before but have never been able to solve it. Any ideas?
It sounds like you're having CSRF issues. When you navigate to the form in your browser, Laravel stores a special token in your browser's cookies and in the request headers. Then, when you submit the form it looks for that token to make sure you are the one submitting the form.
When you test with PHPUnit, that token isn't sent, so you either have to send it yourself, or you have to exclude your tests from the middleware that checks for the token. Excluding your tests is the easier method.
In Laravel 5.0 I did that by overriding the handle() method in the VerifyCsrfToken middleware.
class VerifyCsrfToken extends BaseVerifier {
// Override the handle() method in the BaseVerifier class
public function handle($request, Closure $next) {
// When the App environment is 'testing', skip CSRF validation.
if (app()->environment() === 'testing') {
return $next($request);
}
return parent::handle($request, $next);
}
}
By default, PHPUnit pulls the environment variables from the phpunit.xml file in your Laravel site root. The APP_ENV variable is the one that controls the application environment name, and it defaults to 'testing'. If yours is different, you then need to change the code I provided to match it.
If middleware is in fact the problem you can exclude it from the tests using the WithoutMiddleware trait:
class IndexControllerTest extends TestCase
{
use Illuminate\Foundation\Testing\WithoutMiddleware;
...
I want to add another condition in AuthController but I don't know how to do it.
In my Users table, I have a Active field. If a User has Active == 0, i want to not let he/she login into the system. I don't know where to add that condition in Laravel 5.1 AuthController.
Please help me with that.
Thanks a lot guys!
You can override the postLogin method of the AuthenticatesUsers trait in your AuthController:
public function postLogin(Request $request)
{
/* PLACE HERE VALIDATION CODE... */
//attempt login but not log in the user
if ( ! Auth::attempt($credentials, false, false) )
{
//wrong credentials: redirect to login
}
//CREDENTIALS OK
//get user model from last attempt
$user = Auth::getLastAttempted();
//if user is not active...
if (! $user->active)
{
//do whathever you want: for example redirect to login
}
//USER IS ACTIVE
//login the user
Auth::login($user, $request->has('remember'));
//redirect where you need
}
Check the original: vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers::postLogin method, to see if you need other instructions inside your overrided method (for example input validation)
You can add your custom postLogin() function in your Auth controller which overrides the default postLogin function with this condition
Auth::attempt(array('email' => $email, 'password' => $password, 'active' => 1))
This way you will only do auth of active users only
Cheers!!
Instead of overriding the whole postLogin function, add this to your AuthController
protected function getCredentials(Request $request)
{
$crendentials=$request->only($this->loginUsername(), 'password');
$crendentials['active']=1;
return $crendentials;
}