Laravel : gates and policies vs normal function - laravel

I did that to test if an user can access to a resource:
I created a function in a "global" service:
class IsAuthorizedToRessource{
public function isAuthorizedToUser($connectedUserId, $userId){
// return true or false
}
}
In my controller I call this service:
protected $isAuthorizedToRessource;
public function __construct()
{
$this->isAuthorizedToRessource = new IsAuthorizedToRessource();
}
public function show(Request $request,$id)
{
if (! $this->isAuthorizedToRessource->isAuthorizedToUser(Auth::user()->id, $id)){
return response()->json("not authorized to this ressource", 403);
}
//....
}
All is running fine. But I saw in the Laravel documentation that there is the "gates" and "policies". Despite of the documentation, I don't understand these concepts. And I have tried a lot of things without success.
My question: is my current technique sustainable? or should I make the effort to use these techniques "gates" and "policies"?
How can I use these techniques? (I specify: I do not use a model - I saw there is a "with" and a "without" model technique).

Related

Laravel authorization policy not working on Show page

I have a laravel app using Policies to assign roles and permissions, i cant seem to access the show page and im not sure what im doing wrong?
If i set return true it still shows a 403 error as well, so im unsure where im going wrong here. The index page is accessable but the show page is not?
UserPolicy
public function viewAny(User $user)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
public function view(User $user, User $model)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
UserController
public function __construct()
{
$this->authorizeResource(User::class, 'user');
}
public function index()
{
$page_title = 'Users';
$page_description = 'User Profiles';
$users = User::all();
return view('pages.users.users.index', compact('page_title', 'page_description', 'users'));
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show($id)
{
$user = User::findOrFail($id);
$user_roles = $user->getRoleNames()->toArray();
return view('pages.users.users.show', compact('user', 'user_roles'));
}
Base on Authorize Resource and Resource Controller documentation.
You should run php artisan make:policy UserPolicy --model=User. This allows the policy to navigate within the model.
When you use the authorizeResource() function you should implement your condition in the middleware like:
// For Index
Route::get('/users', [UserController::class, 'index'])->middleware('can:viewAny,user');
// For View
Route::get('/users/{user}', [UserController::class, 'view'])->middleware('can:view,user');
or you can also use one policy for both view and index on your controller.
I had an issue with authorizeResource function.
I stuck on failed auth policy error:
This action is unauthorized.
The problem was that I named controller resource/request param with different name than its model class name.
F. ex. my model class name is Acknowledge , but I named param as timelineAcknowledge
Laravel writes in its documentation that
The authorizeResource method accepts the model's class name as its first argument, and the name of the route / request parameter that will contain the model's ID as its second argument
So the second argument had to be request parameter name.
// Here request param name is timelineAcknowledge
public function show(Acknowledge $timelineAcknowledge)
{
return $timelineAcknowledge->toArray();
}
// So I used this naming here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'timelineAcknowledge');
}
Solution was to name request param to the same name as its model class name.
Fixed code example
// I changed param name to the same as its model name
public function show(Acknowledge $acknowledge)
{
return $acknowledge->toArray();
}
// Changed here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'acknowledge');
}
I looked over Laravel policy auth code and I saw that the code actually expects the name to be as the model class name, but I couldn't find it anywhere mentioned in Laravel docs.
Of course in most of the cases request param name is the same as model class name, but I had a different case.
Hope it might help for someone.

Adding custom where clause to AuthenticatesUser Trait Laravel

We have decided to use Laravel for a project as a test run for future frameworks and are really enjoying it. There is one issue we are having though.
We use the trait Illuminate\Foundation\Auth\AuthenticatesUsers which handles user authentication. It works well. However, we have a column in the database called userstatus which could be a 0 or a 1.
How do we inject this where clause into the Illuminate\Foundation\Auth\AuthenticatesUsers trait?
I was thinking maybe something here (in my LoginController):
public function authenticated($request , $user){
//if $user->userstatus != 1 logout and redirect to start page
}
But I dont know how to logout (im looking into that now) .
your logic is right, you should redefine login and authenticated methods within LoginController.
your methods should be like below:
this method should be within your LoginController.php:
class LoginController extends Controller
{
use AuthenticatesUsers {
login as public loginParent;
}
protected function login(Request $request){
$default = '/';
$user = User::where('email', $request->get('email'))->NotActive->first();
if($user){
return redirect()->intended($default);
}
return $this->loginParent($request);
}
protected function authenticated(Request $request, $user)
{
if($user->not_active) {
$this->logout($request);
}
}
}
then we should create ScopeNotActive method within User.php Model as Local Scope:
//User.php
public function ScopeNotActive($query){
return $query->where('userStatus', '!=', 1);
}
and a Mutator to check if the user is not active:
// User.php
public function getNotActiveAttribute(){
return $this->userStatus != 1;
}

Should I place logic about authentication on the model?

I'm developing an application which involves authentication and files acl.
Now I want to write a method on the file model called "userCanAccess" which check if the given user/ the user role is in the file acl.
The code will be something along those lines:
public function userCanAccess($user = null) {
$user = is_null($user) ? auth()->user() : $user;
if($this->acl->users->contains($user)
|| $this->acl->roles->contains($user->role)) {
return true;
}
return false
}
Is it right to place this kind of logic on the model?
Laravel has a neat built-in bit of functionality called Policies.
You'd create a FilePolicy that applies to the File model:
php artisan make:policy FilePolicy --model=File
and in the resulting app/Policies/FilePolicy.php, you'll see some ready-to-edit existing policies, one of which is called view. Put your authorization logic here.
Once you've built that, you can apply the policy in a variety of ways, like controller functions, middleware on your routes, or directly within views using the #can Blade directive.
https://laravel.com/docs/5.8/authorization#authorizing-actions-using-policies
This should work just fine for me, but rather than bombing the model class, I would extract it to the trait.
You can make roles and permissions tables
User model:
public function roles()
{
return $this->belongsToMany(Role::class);
}
Role model:
public function users()
{
return $this->belongsToMany(User::class);
}
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
Permission model:
public function roles()
{
return $this->belongsToMany(Role::class);
}
then in app/Providers/AuthServiceProvider you can make Gate like this:
public function boot()
{
$this->registerPolicies();
foreach ($this->getPermissions() as $permission) {
Gate::define($permission->name,function($user) use($permission){
return $user->hasRole($permission->roles);
});
}
}
private function getPermissions(){
return Permission::with('roles')->get();
}
at the end you can use ACL everywhere you want by just write Gate name like:show-comments or access-files or ....

Setting getKeyRouteName dependant on route (web or api)

Tried looking for the answer to this everywhere but having no luck so far...
Basically I want my web route to use a slug for its URL, but I want to use ID for the API route. So...
http://myurl.com/chapter/my-chapter-slug
and
http://myurl.com/api/chapter/1234
Have tried various combinations of things in the getRouteKeyName method (if(Request::route()->named('myapiroute'), if(Request::isJson() etc...) but I think these might be being checked against the page it's running on, rather than the route I'm trying to generate?
I'm thinking maybe I need to extend the base model to have a separate one to use with my API maybe?
So I'd have...
class Chapter extends Model
{
public function getRouteKeyName()
{
return 'slug';
}
....
}
and then...
class ApiChapter extends Chapter
{
public function getRouteKeyName()
{
return 'id';
}
....
}
But not sure how I'd structure this in the most "Laravel" way? Or is there a better/tidier solution?
define your route for example like
Route::get('chapters/{chapter}','ChapterController#show'); // find by slug
Route::get('api/chapters/{chapter}','ApiChapterController#show'); // find by id
for web controller
class ChapterController extends Controller
{
public function show(Request $request,$slug)
{
$instance = Model::whereSlug($slug)->first();
}
}
for api
class ApiChapterController extends Controller
{
public function show(Request $request,$id)
{
$instance = Model::find($id);
}
}
You can define 2 different routes for that but unfortunatelly you will not be able to use model binding and you will have to look for the model like:
public function show(Request $request,$slug) {
$instance = Model::whereSlug($slug)->first();
}
as shown below: https://stackoverflow.com/a/48115385/6525417

Codeigniter models: only return not load? Are models just namespaces?

I'm finding out about how CI scopes things a bit late. I've been creating models like this:
$this->load->model('user');
$this->user->load ($user_id);
Then I'd pass around the $this->user object to be able to access all the various things I needed from that object, update properties and such.
I downloaded a Phil Sturgeon CI app callend PyroCMS and I see that he mostly returns data from his object's methods, much like a straight-up procedural function.
So, are models really only supposed to be used at namespaces in CI?
I'm finding that using them the way I am, with a just-now-discovered scope issue, I'm over-writing my models.
Of course the solution is the name it when loading, but that means I have to track and be wary of what name each one of them is using, which is going to be a problem.
Is this how others use the CI models, mainly returning things from them instead of using them as full featured objects?
I found Phil Sturgeon responded to this question: Codeigniter models are just utility classes? with essentially what I need to know. I can still use the loaded model by using the php $object = new Class syntax. I will do this:
class Companies
{
private $_users;
public function __construct ()
{
$this->load->model ('users');
$this->_users = new Users;
}
}
With the private and the new I think I'm safe finally. Probably I should go ahead and do that outside of the model, and not in the constructor, then pass it in as a dependency. I had given up on DI.
I think I've talked myself off the ledge.
After 2 years with CI, here's how I've begun to use the models:
// Singleton class to lookup Users and perform other
// tasks not related to one specific user
class User_model extends MY_Model {
public static $CI;
public function __construct()
{
parent::__construct();
self::$CI =& get_instance();
}
public function getByEmail($email)
{
$data = $this->db->where('email', $email)->get('users')->first_row();
if ($data)
{
$user = new User;
return $user->load($data);
}
}
public function getAllUsers()
{
$data = $this->db->get('users')->result();
foreach ($data as &$row)
{
$user = new User;
$row = $user->load($row);
}
return $data;
}
//... other functions that makes sense in a singleton class
}
// Actual user object. Instantiate one for every user you load...
class User {
public function __construct($id)
{
$data = User_model::$CI->db->where('id', $id)->first_row();
$this->load($data);
}
public function load($data)
{
foreach ($data as $k => $v)
{
$this->$k = $v;
}
return $this;
}
}

Resources