Public Accessible with Laravel Policy - php

I want to make some entries of Analysis publicly available. I tried to implement it with Policies but failed. I think it's because the AuthServiceProvider fails with AccessDeniedHttpException every time I try to access without an authorized user.
AuthServiceProvider
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Analysis::class => AnalysisPolicy::class
];
public function boot()
{
$this->registerPolicies();
}
}
AnalysisPolicy
public function view(User $user, Analysis $analysis)
{
if($analysis->demo === true){
return true;
}
return $user->id === $analysis->user_id;
}
AnalysisController
public function show(int $analysis)
{
$ana = Analysis::find($analysis);
$this->authorize('view', $ana);
...
}
I tried to just create a new Service Provider, but that didn't work either as I cannot call the registerPolicies function without extending from AuthServiceProvider.
Basically, all I want is to now check for anything if the demo Attribute is true.
Edit:
My Quick-Fix form now is just checking in the controller if it's a demo. But that's not a great solution in my opinion as I think the goal with Policies should be that I don't have Access Management in the Controller. So I'd love to find a better solution.
if(!$ana->demo){
$this->authorize('view', $ana);
}

Ok so it turns out you should always read the release notes of the latest Laravel version before asking a question.
As of Laravel 5.7 there is a proper solution for this:
public function update(?User $user, Post $post)
{
return $user->id === $post->user_id;
}
(https://laravel.com/docs/5.7/authorization#writing-policies)
By declaring $user optional, it's null for guest users and can be handled in the policy.

Related

How to bind authenticated user to parameter in controller?

I cant retrieve authenticated webmaster in controller. As you can see below, i authenticate user in constructor via $this->middleware:
class DomainController
.....
public function __construct()
{
$this->middleware('auth:webmasters');
}
public function requestNewName(Request $request, Webmaster $webmaster, DomainRepositoryInterface $domainRepository): array
{
// $webmaster->id === null here
/** #var Webmaster $webmaster */
$webmaster = Auth::user(); // $webmaster->id === 1, all OK
$domainRepository->requestChangeName($webmaster, $request->input('newName', ''));
return ['result' => true];
}
....
I think i need to bind it somewhere, but i dont understand where or how?
P. S.
Now i have in AuthServiceProvider:
foreach ([Webmaster::class, Admin::class] as $class) {
$this->app->bind($class, static function($app) use ($class) {
$authenticated = Auth::user();
/** #noinspection GetClassUsageInspection */
return $authenticated && get_class($authenticated) === $class ? $authenticated : null;
});
}
}
And call this function in boot method. I bet that laravel has something for it.
You want the webmaster to be injected into your method by laravel's dependency injection.
The way you would do this is through the Service Container, which is the guy who handles the injections. When you as for a Webmaster $webmaster, it looks for a binding with that type, since you haven't done an explicit bind, it tries to give you an instance anyways, but that's a generic one.
All you gotta do is add this code in your service provider:
$this->app->bind('App\Webmaster', function ($app) {
return Auth::user();
});
Now laravel knows how you want Webmaster to be injected into the function.
This should be a comment but I don't have enough reputation. Your code looks ok to me. But one thing comes to mind.
Since $webmaster->id gives you null means that the Webmaster class is imported properly in the DomainController. But maybe you haven't imported the Webmaster class properly in the AuthServiceProvider. Also maybe dd(get_class($webmaster)); will help. What does Auth::user() return in your app, anyway?

Laravel resource policy always false

I'm trying to allow user to view their own profile in Laravel 5.4.
UserPolicy.php
public function view(User $authUser, $user)
{
return true;
}
registered policy in AuthServiceProvider.php
protected $policies = [
App\Task::class => App\Policies\TaskPolicy::class,
App\User::class => App\Policies\UserPolicy::class
];
Routes
Route::group(['middleware' => 'auth'], function() {
Route::resource('user', 'UserController');
} );
Blade template
#can ( 'view', $user )
// yes
#else
// no
#endcan
UserController.php
public function profile()
{
return $this->show(Auth::user()->id);
}
public function show($id)
{
$user = User::find($id);
return view('user.show', array( 'user'=>$user,'data'=>$this->data ) );
}
The return is always 'false'. Same for calling policy form the controller. Where do I go wrong?
Answering my own question feels weird, but I hate it when I come across questions without followups.
So after double checking It turned out that if I remove authorizeResource from the constructor:
public function __construct()
{
$this->authorizeResource(User::class);
}
and check for authorization in the controller function:
$this->authorize('view',$user);
everything works.
I must've missed this part when I added $user as a parameter in the policy function. So the user to be viewed is never passed in the authorizeResource method.
Thanks everyone for taking your time to help me.
When you add
public function __construct()
{
$this->authorizeResource(User::class);
}
to your Controller, you have to edit all your function signatures to match it to the class e.g. your show signature has to change from public function show($id)
to public function show(User $user)
After that it should work
Just a different approach here to users viewing their own profile.
First, I will create a route for that
Route::group(['middleware' => 'auth'], function() {
Route::get('profile', 'UserController#profile');
});
Then in the profile function I do
public function profile()
{
$user = Auth::user();
return view('profile', compact('user'));
}
This way, user automatically only views their own profile.
Now, if you want to allow some users to view others' profiles, then you can use Policy. Why? Because I think user should ALWAYS be able to view their own profile. But not all users should view other users profiles.
Solution:
Change the second parameter from #can( 'view', $user ) to #can( 'view', $subject ) and it will work find.
Why:
Because you're doing it the wrong way.
public function view(User $user, $subject){
return true;
}
Just look carefully the policy view method, first parameter is authenticated user or current user and second parameter is $subject, Since policies organize authorization logic around models.
Policies are classes that organize authorization logic around a
particular model or resource. For example, if your application is a
blog, you may have a Post model and a corresponding PostPolicy to
authorize user actions such as creating or updating posts.
if you want to go further deep inside it.
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/Access/Gate.php#L353
/**
* Resolve the callback for a policy check.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
// If we receive a non-null result from the before method, we will return it
// as the final result. This will allow developers to override the checks
// in the policy to return a result for all rules defined in the class.
if (method_exists($instance, 'before')) {
if (! is_null($result = $instance->before($user, $ability, ...$arguments))) {
return $result;
}
}
if (strpos($ability, '-') !== false) {
$ability = Str::camel($ability);
}
// If the first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument list
// because the policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$instance, $ability])) {
return false;
}
return $instance->{$ability}($user, ...$arguments);
};
}
See the last line where it is calling the method with $user and $argument( in our case Model ) is passed.
Laravel Docs for Authorization/Policies
It's possible to escape one or more policies methods using options parameter at authorizeResource with except:
public function __construct()
{
$this->authorizeResource(User::class, 'user', ['except' => ['view']]);
}
This should be on Laravel's documentation, but it isn't. I discovered this just guessing. I think this way it is a better approach thus, by removing authorizeResource method in the construct, it would be necessary to implement the authorization method for each resource action in order to protect the controller.

Laravel 5.3 - Sharing $user variable to all views

So after finding out that sharing views in Controller.php's constructer no longer works because it always returns null to Auth::user(), I am looking for a different way to do it.
I am simply looking for a way to pass a $user variable with the current signed in user to all my views.
Previous way which worked in 5.2 and below:
public function __construct()
{
view()->share('signed_in', Auth::check());
view()->share('user', Auth::user());
}
This no longer works. How else can I share variables?
I have tried:
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
$this->signed_in = Auth::guest();
view()->share('signed_in', $this->signed_in);
view()->share('user', $this->user);
return $next($request);
});
}
But the code above does not work. It does load the page without a "Undefined Variable $user" error but it just show the navigation bar and then nothing else. It also messes up the site CSS for some reason.
Is there any other way I can do it?
Please help. Thank you.
I fixed this issue quite easily.
In my \App\Http\Controllers\Controller.php
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
private $user;
private $signed_in;
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
$this->signed_in = Auth::check();
view()->share('signed_in', $this->signed_in);
view()->share('user', $this->user);
return $next($request);
});
}
}
By putting the view()->share() in a closure of a middleware, I was able to achieve this.
Yes, for example, you could create a new service provider, and register it in your config/app.php, and put the share logic in there.
Let's start with creating a new service provider:
~/path/to/project$ php artisan make:provider ShareWithViewServiceProvider
You should see the message Provider created successfully.
Then you should edit your config/app.php, and add the new service provider you've just created:
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ShareWithViewServiceProvider::class, // <-- This is the new entry
Then create your logic in the boot method of your service provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ShareWithViewServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
view()->share([
'user' => auth()->user(),
'signedIn' => auth()->check(),
]);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
Please keep in mind that auth()->user() will return null if you are not signed in.
As a shorter solution, you can just put the share logic in the app\Providers\AppServiceProvider.php, which Laravel creates by default, and has nothing in it (it's there for us to use).
Using the middleware callback the way you do is a correct way to go about this, but your assignment is wrong.
Use this instead:
$this->signed_in = Auth::check();

How to create Laravel 5.1 Custom Authentication driver?

I am working in Laravel authentication login using socialite. Now I can able to save data of user from socialite. But now I am facing problem how to authenticate user from gmail, github.
After some research I understood that I need to create custom authentication. I googled but all are Laravel 4.1 topics. If any one work on this please provide your answers.
I already read following topics but I didn't got how to do it?
http://laravel.com/docs/5.1/authentication#social-authentication
http://laravel.com/docs/5.1/providers
http://laravel-recipes.com/recipes/115/using-your-own-authentication-driver
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Update
public function handleProviderCallback() {
$user = Socialite::with('github')->user();
$email=$user->email;
$user_id=$user->id;
//$authUser = User::where('user_id',$user_id)->where('email', $email)->first();
$authUser = $this->findOrCreateUser($user);
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
}
private function findOrCreateUser($user) {
if ($authUser = User::where('user_id',$user->id)->first()) {
return $authUser;
}
return User::create([
'user_id' => $user->id,
'name' => $user->nickname,
'email' => $user->email,
'avatar' => $user->avatar
]);
}
This answer is most suited for Laravel 5.1. Please take care if you
are in some other version. Also keep in mind that IMHO this is a rather advanced level in Laravel, and hence if you don't fully understand what you are doing, you may end up crashing your application. The solution is not end to end correct. This is just a general guideline of what you need to do in order for this to work.
Adding Custom Authentication Drivers In Laravel 5.1
Hint: Laravel documentation for this topic is here.
Hint2: The last link you mentioned is quite useful in my opinion. I learned all of this after reading that link.
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Before we start, I would first like to describe the login flow which will help you understand the process. Laravel uses a driver to connect to the database to fetch your records. Two drivers come pre-bundled with laravel - eloquent & database. We want to create a third so that we can customize it to our needs.
Illuminate\Auth\Guard inside your vendor folder is the main file which has code for the user to log in and log out. And this file mainly uses two Contracts (or interfaces) that we need to override in order for our driver to work. From Laravel's own documentation read this:
The Illuminate\Contracts\Auth\UserProvider implementations are only
responsible for fetching a Illuminate\Contracts\Auth\Authenticatable
implementation out of a persistent storage system, such as MySQL,
Riak, etc. These two interfaces allow the Laravel authentication
mechanisms to continue functioning regardless of how the user data is
stored or what type of class is used to represent it.
So the idea is that for our driver to work we need to implement Illuminate\Contracts\Auth\UserProvider and Illuminate\Contracts\Auth\Authenticatable and tell Laravel to use these implementations instead of the defaults.
So let's begin.
Step 1:
Choose a name for your driver. I name mine socialite. Then in your config/auth.php, change the driver name to socialite. By doing this we just told laravel to use this driver for authentication instead of eloquent which is default.
Step 2:
In your app/Provider/AuthServiceProvider in the boot() method add the following lines:
Auth::extend('socialite', function($app) {
$provider = new SocialiteUserProvider();
return new AuthService($provider, App::make('session.store'));
});
What we did here is:
We first used Auth facade to define the socialite driver.
SocialiteUserProvider is an implementation of UserProvider.
AuthService is my extension of Guard class. The second parameter this class's constructor takes is the session which laravel uses to get and set sessions.
So we basically told Laravel to use our own implementation of Guard class instead of the default one.
Step 3:
Create SocialiteUserProvider. If you read the Laravel's documentation, you will understand what each of these methods should return. I have created the first method as a sample. As you can see, I use my UserService class to fetch results. You can fetch your own results however you want to fetch them. Then I created an User object out of it. This User class implements the Illuminate\Contracts\Auth\Authenticatable contract.
<?php
namespace App\Extensions;
use App\User;
use App\Services\UserService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class SocialiteUserProvider implements UserProvider
{
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function retrieveById($identifier)
{
$result = $this->userService->getUserByEmail($identifier);
if(count($result) === 0)
{
$user = null;
}
else
{
$user = new User($result[0]);
}
return $user;
}
public function retrieveByToken($identifier, $token)
{
// Implement your own.
}
public function updateRememberToken(Authenticatable $user, $token)
{
// Implement your own.
}
public function retrieveByCredentials(array $credentials)
{
// Implement your own.
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
// Implement your own.
}
}
Step 4:
Create User class which implements the Authenticatable. This class has to implement this interface because the Guard class will use this class to get values.
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
class User implements Authenticatable
{
protected $primaryKey = 'userEmail';
protected $attributes = [];
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function getUserAttributes()
{
return $this->attributes;
}
public function getAuthIdentifier()
{
return $this->attributes[$this->primaryKey];
}
public function getAuthPassword()
{
// Implement your own.
}
public function getRememberToken()
{
// Implement your own.
}
public function setRememberToken($value)
{
// Implement your own.
}
public function getRememberTokenName()
{
// Implement your own.
}
}
Step 5:
Finally create the AuthService class that will call the Guard methods. This is my own implementation. You can write your own as per your needs. What we have done here is extended the Guard class to implement two new functions which are self explanatory.
<?php
namespace App\Services;
use Illuminate\Auth\Guard;
class AuthService extends Guard
{
public function signin($email)
{
$credentials = array('email' => $email);
$this->fireAttemptEvent($credentials, false, true);
$this->lastAttempted = $user = $this->provider->retrieveById($email);
if($user !== null)
{
$this->login($user, false);
return true;
}
else
{
return false;
}
}
public function signout()
{
$this->clearUserDataFromStorage();
if(isset($this->events))
{
$this->events->fire('auth.logout', [$this->user()]);
}
$this->user = null;
$this->loggedOut = true;
}
}
Step 6: Bonus Step
Just to complete my answer, I will also explain the structure that UserService class expects. First lets understand what this class does. In our above steps we created everything to let laravel know how to use our authentication driver, instead of theirs. But we still haven't told laravel that how should it get the data. All we told laravel that if you call the userService->getUserByEmail($email) method, you will get your data. So now we simply have to implement this function.
E.g.1 You are using Eloquent.
public function getUserByEmail($email)
{
return UserModel::where('email', $email)->get();
}
E.g.2 You are using Fluent.
public function getUserByEmail($email)
{
return DB::table('myusertable')->where('email', '=', $email)->get();
}
Update: 19 Jun 2016
Thank you #skittles for pointing out that I have not clearly shown where the files should be placed. All the files are to be placed as per the namespace given. E.g. if the namespace is App\Extensions and the class name is SocialiteUserProvider then location of file is App\Extensions\SocialiteUserProvider.php. The App directory in laravel is the app folder.
Good tutorial for setting up laravel socialite here: https://mattstauffer.co/blog/using-github-authentication-for-login-with-laravel-socialite
Auth::login doesn't return a boolean value you can use attempt to do a Auth::attempt
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
Follow the tutorial and do this, and just have middleware configured on the home route
$authUser = $this->findOrCreateUser($user);
Auth::login($authUser, true);
return Redirect::to('home');

Laravel, Auth and logging out from a model

I'm currently using Laravel 5 Authentification, but I have edited it to allow me to connect to an API server instead of an Eloquent model.
Here is the code of my custom UserProvider:
<?php namespace App\Auth;
use Illuminate\Contracts\Auth\UserProvider as UserProviderInterface;
use WDAL;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Auth\GenericUser;
use Session;
class WolfUserProvider implements UserProviderInterface {
private $_loggedUser;
public function __construct()
{
$this->_loggedUser = null;
$user = Session::get('user');
if (!empty($user)) {
$this->_loggedUser = unserialize($user);
}
}
public function retrieveById($id)
{
return $this->_loggedUser;
}
public function retrieveByToken($identifier, $token)
{
return null;
}
public function updateRememberToken(Authenticatable $user, $token)
{
//dd('updateRememberToken');
}
public function retrieveByCredentials(array $credentials)
{
$user = WDAL::getContactCredentials($credentials['login']);
return $user;
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
if($user->username == $credentials['login'] && $user->password == $credentials['password']){
$this->_loggedUser = $user;
Session::set('user', serialize($user));
return true;
}
else{
return false;
}
}
}
?>
This code might not be perfect as it still in early development ;-) (feel free to suggest me some ideas of improvement if you want to)
So when the user is logged, it has access to the whole platform and to several views and can communicate with the API server to display and edit data.
Sometimes, the API server can return "Invalid Session ID" and when my Model gets this message, the user should be redirected to the login page.
From a Controller it's really easy to handle I can use this code (logout link):
public function getLogout()
{
$this->auth->logout();
Session::flush();
return redirect('/');
}
But do you know how I should proceed from a Model ? I could of course edit all my controllers to check for the value returned by the Model to logout, but cannot it be done thanks to middlewares?
It seems to be really long to edit all my controllers, and this will imply a lot of duplicated code.
One of my tries was to throw an exception from the Controller, and catch in from the auth middleware.
It was not working, because I didn't write use Exception;
I'm now catching the exception, and can now redirect the user from the middleware.
Thank you anyway!

Categories