I have four table users, groups, posts, and group_user. Users can follow different groups. The group_user table is for many-to-many relationship.
Every post belongs to a group and a user. I want that a user can only post a post on a group if he follows that group. I can easily check using if statement that whether a user follows that group or not. But how can I authorize the user for posting using policies.
Create a policy:php artisan make:policy.
Register policy:
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
In your policy the logic, for instance:
public function create(User $user, Post $post)
{
$groupsOfUser = $user->groups()->get();
foreach ($groupsOfUser as $group) {
if($group->id == request('groupId'))return true;
}
}
And in your Controller:
public function store(Post $post, $groupId)
{
$this->authorize('create', $post);
Post::create([
'user_id' =>auth()->id(),
'group_id' => $groupId,
'title' => 'sometitle',
]);
}
And i have tested it with route:
Route::get('/post/{groupId}', 'PostController#store');
But you may be getting groupId via input, but you get the idea.
There doesn't seem to be any good answers for this so I'll share what I did to help out anyone looking for this in the future.
This was tested in laravel 5.8 but I think it will work at least a few versions back.
First create a pivot model
use Illuminate\Database\Eloquent\Relations\Pivot;
class GroupUser extends Pivot {}
Update your groups relationship in the User model
public function groups() {
return $this->belongsToMany(Group::class)
->using(GroupUser::class);
}
And update your users relationship in the Group model
public function users() {
return $this->belongsToMany(User::class)
->using(GroupUser::class);
}
Create your GroupUserPolicy class
use Illuminate\Auth\Access\HandlesAuthorization;
class GroupUserPolicy {
use HandlesAuthorization;
public function update(User $user, GroupUser $pivot) {
// check permissions
}
// additional authorization methods
}
And link them up in the AuthServiceProvider if not using auto discover
protected $policies = [
GroupUser::class => GroupUserPolicy::class,
// ...
];
Then when you want to show or update a pivot, in a controller for example, then you can just pass the pivot to the authorize check
$group = $user->groups()->where('group_id', $groupId)->firstOrFail();
$this->authorize('update', $group->pivot);
// or ...
foreach ($user->groups as $group) {
$this->authorize('update', $group->pivot);
}
There is a complex solution for Access Control List. Read about Zizaco Entrust Library. Here you can set permissions for each route in your system. By that routing and permission you can prepare few groups.
Topic is hard but realy worth to implement:
https://github.com/Zizaco/entrust
Related
I'm using simple Gates user permissions on my Laravel.
I have a permissions table with hasOne relationship:
class User extends Authenticatable
{
public function permissions()
{
return $this->hasOne(UserPermission::class);
}
}
and in an AuthServiceProvider I have to register permission:
public function boot()
{
$this->registerPolicies();
Gate::define('is_admin', fn(User $user) => $user->permissions->is_admin);
Gate::define('is_test_user', fn(User $user) => $user->permissions->is_test_user);
//
}
but when the record doesn't exist in my permission table then I have an error: Attempt to read property "is_admin" on null.
What's the best solution for this problem?
The optional helper is intended for this purpose.
optional($user->permissions)->is_admin
I need help, don't see anything suspicious :c thanks for help !
Error Collection::addEagerConstraints does not exist occurs after the call:
public function show(Request $request, User $user)
{
$user->load('permissions');
dd($with);
return UserResource::make($user);
}
User Model:
class User extends Authenticatable
{
(...)
//////////////////////////////////////////////////////////////////////////////////////////
///
/// Relationships
///
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Relationship to permissions
*
* #return RolePermissions
*/
public function permissions()
{
return $this->role()->first()->permissions;
}
}
if you are using standard laravel user ,
you have to remove your 'permissions' relation and use the ready-made one:
$permissionNames = $user->getPermissionNames(); // collection of name strings
$permissions = $user->permissions; // get the user permissions
if you want a user with its permissions:
$user->load('permissions');
more details in:
https://docs.spatie.be/laravel-permission/v3/basic-usage/basic-usage/
Can' create Policy for User model.
I created Policy like this
php artisan make:policy UserPolicy --model=User
Got UserPolicy.php with CRUD actions.
Then inside AuthServiceProvider.php I added
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
];
But nothing happens. As I understand generated Policy for User model by default returning false on every action, I even explicitly added this to UserPolicy class:
public function create(User $user)
{
return false;
}
Still can create user.
Later I will need to check if the user trying to edit his own post or not. Everything should be forbidden for non-admin users except editing own profile (model).
I must be missing something obvious.
UPDATE:
If I put
$this->authorize('create', $user);
In UsersController create method, it will invoke create method Policy, so it seams that something is wrong with
...
use App\Policies\UserPolicy;
use App\User;
...
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
];
inside AuthServiceProvider.
You can write this code for User Policy
in UserPolicy.php :
public function update(User $user, User $model)
{
return $user->id === $model->id;
}
For example by this code you can update just your own profile.
You need to put this in you function in controller
$this->authorize('create',$user)
Put below lines in your controller function $this->authorizeForUser($currentUser,'create', User::class)
Suppose there is a Test Model like this:
class Test extends Model
{
public $primaryKey = 'test_id';
public function questions ()
{
return $this->belongsToMany('App\Question', 'question_test', 'test_id', 'question_id')->withPivot('weight');
}
}
And a Question Model like this :
class Question extends Model
{
public $primaryKey = 'question_id';
public function tests ()
{
return $this->belongsToMany('App\Test', 'question_test', 'question_id', 'test_id')->withPivot('weight');
}
}
Question model fields are these:
question_id
text
correct
active => can be true or false
created_at
updated_at
As you see there is a ManyToMany Relationship between these two models.
And I have two separate sections in my app, one for Admin users and other for public users.
Admin can perform any action on questions and tests. like add some question to tests , remove , edit and etc.
But in the other hand , public users can only tests and related question that are active(means their active fields are true).
Suppose some Admin routes are these:
http://myapp.dev/Admin/tests
http://myapp.dev/Admin/test/5/questions
http://myapp.dev/Admin/test/5/question/create
http://myapp.dev/Admin/test/5/remove
And some User routes are:
http://myapp.dev/Dashboard
http://myapp.dev/tests-list
http://myapp.dev/test/5/questions
For that , I know that I can use query-scopes like this in Question model :
public function scopeActive($query)
{
return $query->where('active', 1);
}
And when I want to fetch only active questions must to do this:
$test->questions->active()->get();
But I have many action that perform on questions on the user Panels therefore that is hard and time consuming if want to use an active() method for select questions.
I can not use Global scopes because that Affects on all question queries that run in whole project.
Is there a way that can define global (or local ) scope for some specific routes and sub routes that public users can see those?
Or there are other ways to solve this problem?
Update:
In addition to those mentioned, Users can have some roles. For example an admin user can switch from admin panel to user panel. in this case I want to display only active question when he is on user panel.
You can do this by wrapping your routes in middleware. Create HideInactiveQuestions.php by running php artisan make:middleware HideInactiveQuestions
<?php
namespace App\Http\Middleware;
use App\Question;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class HideInactiveQuestions
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
Question::addGlobalScope('active', function(Builder $builder) {
$builder->where('active', '=', 1);
});
return $next($request);
}
}
Next, register your middleware in Kernel.php
protected $routeMiddleware = [
....
'restrict.public' => \App\Http\Middleware\HideInactiveQuestions::class,
];
Now those routes which have been wrapped by this middleware (restrict.public) will be filtered by your scopes.
I am new on Laravel and use Authorization. I am looking for the way to change default sql for Auth. Actually, Laravel does it using this simple sql command at below:
SELECT * FROM users WHERE login="something" AND password = "something" LIMIT 1
I am trying to change default sql like this:
SELECT
u.id, u.name, c.company
FROM
users u, companies c
WHERE
u.login="something" AND
u.password = "something" AND
u.companyId = c.id
LIMIT 1
I understood that I should create custom Authorization system: crate new user Provider and Auth Provider.
Firstly, I created Auth folder inside App and added there CustomUserProvider.php
CustomUserProvider.php
<?php namespace App\Auth;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider as UserProviderInterface;
use App\Models\User;
class CustomUserProvider implements UserProviderInterface {
protected $model;
public function __construct(UserContract $model)
{
$this->model = $model;
}
public function retrieveById($identifier)
{
}
public function retrieveByToken($identifier, $token)
{
}
public function updateRememberToken(UserContract $user, $token)
{
}
public function retrieveByCredentials(array $credentials)
{
}
public function validateCredentials(UserContract $user, array $credentials)
{
}
}
My customAuthProvider.php file, in App/Providers:
<?php namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Auth\CustomUserProvider;
use Illuminate\Support\ServiceProvider;
class CustomAuthProvider extends ServiceProvider {
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
$this->app['auth']->extend('custom',function()
{
return new CustomUserProvider(new User);
});
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
At the end I set driver to custom in config/Auth.php
'driver' => 'custom'
I am looking for the way using this custom classes how can I use custom sql command for Authorization (Login)?
Or maybe this way is wrong?
If all you need are additional constraints on the query that fetches user from the database during authentication, there is much simpler way to do that.
First of all, Laravel offers an AuthenticatesUsers trait that you can use in your controller to handle authentication requests. The default implementation fetches user from the database using username field and then, if matching user is found, it validates their password.
The list of attributes that is used to fetch user from the database can be customized by overriding getCredentials method in your controller. In your case the following should be enough to load user using their username and company id:
protected function getCredentials(Request $request)
{
return $request->only($this->loginUsername(), 'password', 'companyId);
}
Once you add that, user should provide their username, companyId and password in the login form and they will be authenticated only if there exists a user with given username that belongs to given company and the password provided is valid.
UPDATE: If you decide not to use the trait, but want to authenticate users manually, you can do so in a really similar manner. When calling Auth::attempt() you just need to pass all the criteria that should be used to authenticate the user, e.g.:
Auth::attempt([
'username' => Input::get('username'),
'companyId' => Input::get('companyId'),
'password' => Input::get('password')
]);
I tried this package and it helped me:
https://github.com/ollieread/multiauth/