Middleware for public storage in Laravel - php

I have this structure: laravel/storage/app/public/images/id_of_user/
Where id of user is the id of the user.
This is my middleware:
public function handle($request, Closure $next)
{
if($request->user_id == Auth::user()->id{
return $next($request);
}
return back();
}
and made a route like this:
Route::group(['middleware' => 'storagemiddleware'], function () {
Route::get('storage/images/{$user_id});
});
This doesn't work, tho. Everyone can still acess to everyones images. I honestly didn't expect this to work since it seems that storage has its own special routes and settings.
What I want is that only the users are allowed to see the content of their folder in their storage.
Any suggestions?

Read through this section of the documentation: https://laravel.com/docs/5.4/filesystem.
If the idea is to keep files accessible only to those you own them, you'll want to put them somewhere in the storage directory and then access them through a controller. Anything in your public directory is just that, public.
You could setup a directory structure like this storage/user_data/<$user_id>/ to keep things separated, then retrieve them from your route and the specified parameter.

Related

Laravel Routes using Middleware with an OR in list of Middleware or other method

I am using the Spatie permission and roles package for Laravel. I wanted to try and check the permission and roles in my web routes.php file in Route Groups, but that doesn't seem to work because the Auth::user() does't have the Spatie functions attached in the routes. That must happen somewhere else. I say that because I get a call to member function on NULL.
What I am currently doing is something like this:
Route::group(['middleware' => ['auth:sanctum', 'verified']], function () {
Route::post('/Reports/getallhl7_reports', [ReportsController::class, 'getallhl7_reports'])->name('getallhl7_reports');
Route::post('emailReport', [UtilitiesController::class, 'emailReport'])->name('emailReport');
});
and then in the Controllers checking the permissions and roles like:
protected function getallhl7_reports(Request $request) {
if (Auth::user()->can('provider_data') || Auth::user()->can('staff_data')) {
return Reports::getallhl7_reports($request->input('StudyInstanceUID'));
}
}
What I might want to do is to check the permissions in the routes or routes groups instead of in the Controller, but like I said the Spatie functions are not available in the Route Groups (After the Group, but before the Route) for some reason, and using an OR in the Middleware seems to be problematic. You can check for permissions in the Group, but I think that is an AND and not an OR, like:
Route::group(['middleware' => ['auth:sanctum','verified', 'permission:provider_data', 'permission:staff_data']], function () {
It isn't clear to me how I could check permissions using this format:
Route::post('emailReport', [UtilitiesController::class, 'emailReport'])->name('emailReport');
by modifying it with something to verify some set of permissions before invoking the Controller.

Grouping all the laravel application routes using dynamic prefix

We have multiple client portal each one has a unique url like
xyz.com/ClientPotal123
xyz.com/ClientPotal234
xyz.com/ClientPotalXXX
We will be routing all these url's to
/var/www/html/Laravelapp/public
Laravelapp is our codebase which we use for all the clients.
Since ClientPotalXXX is dynamic and unique for all the clients, I need to get the value of ClientPotalXXX for loading client specific settings like url generation, database connection (We have different database for each client).
To achieve above I've done below changes..
My Web.php file is as below..
Route::pattern('ClientPortal','^ClientPortal([0-9]+)?');
Route::prefix('/{ClientPortal}')->group(function () {
Route::get('/user/list', 'UserController#list')->name('list');
Route::get('/user/edit/{id}', 'UserController#edit');
});
I've created Middleware with below code written in it..
public function handle($request, Closure $next)
{
$database_name = strtolower($request->ClientPortal).'_db';
config(['database.connections.mysql.database'=>$database_name]);
config(['app_settings.client'=>$request->ClientPortal]);
return $next($request);
}
And it's working fine but previously I used to access $id in edit function directly
public function edit($id){
echo $id; // 12
}
But now $id return the value of ClientPortalXX everytime.
If I access id from Request it works fine
public function edit(Request $request){
$id = $request->id; // 12
}
This is happening with all of the other routes where I'm using route parameters.
So I'm not sure if this happened because I'm using dynamic prefix for grouping all the routes?
And now for every route() method which I've used in blade files for url generation I have to pass the second parameter ie. Value of {ClientPortal}
{{route('register',['ClientPortal'=>config('app_settings.client')])}}
Is this right implementation? I know we can make any varibale accessible globally using service provider but will it be right to do so?
My Laravel Version is 5.5.xx.. I'm just a beginner so any Help/Suggestion/Advice will be appreciated Thanks :)
Update:
Nikola Gavric and Oluwafemi Sule had already clarified my doubt in comments below.
But since the group prefix is dynamic, How do I handle the route naming case?
If I had to generate user list url using list route name which is mentioned in above web.php file.
Now I've to change this line..
{{ route('list') }}
To
{{ route('list',['ClientPortal'=> 'ClientPortalXXX' ]) }}
Since prefix is also a route param.
Is this feasible option? Because I've to do this change everywhere where I've used route method for url generation.

Laravel: Checking if user has permission

I'm writing a server based solution. In database there are many users with different permissions and I have to check if they have permission to access module they are trying to.
In every Controller I have included something like:
protected $module = "moduleName";
I tried to solve it like:
function __construct()
{
$perm = session()->get('perm');
if (!isset($perm[$this->module]) || !$perm[$this->module]) {
Session::flash('message_error', "<span class='glyphicon glyphicon-warning-sign'></span> Access denined!");
return back();
}
}
It shows the message but it still displays the page not redirects back.
As you see I'm reading permissions from session and modules name is saved in controller so I don't think this could be solved by middleware unless I'm making middleware for each module (I'm talking about 30 modules).
Thanks for taking the time to read this
Middleware actually solved this.
Route:
Route::group(['middleware' => 'module:moduleName'], function () {
// Routes...
});
Custom middleware:
public function handle($request, Closure $next, $module)
{
$perm = session()->get('perm');
if (!isset($perm[$module]) || !$perm[$module]) {
Session::flash('message_error', "<span class='glyphicon glyphicon-warning-sign'></span> Access denined!");
return redirect()->back();
}
return $next($request);
}
Also I'll mention that Route groups can be nested. So you can wrap multiple groups with something like auth middleware as well
There is a very easy fix to your code, you forgot the define the redirect, so instead of using
return back();
use
return redirect()->back();
This will do the redirect.
There is also a mistake in your reasoning, you could and probably should use middleware.
Middleware does have access to the user, session and can be passed parameters. These are the necessary requirements for your system.
You can also assign middleware on a controller basis.

Secure Asset/Media Folder through Auth Controller ? Laravel 5.2

I have public/Asset/Media/folder
I can access this file publicly like below.
http://localhost/myapp/public/Asset/Media/1/phpunit.xml
Similarly there are other folders in the Asset/Media folder which are being created on the fly.
There are many files also present in those sub folder and are also present in Asset/Media folder
Is there any way, such that if I try to access any file in Asset/Media folder or any file present in the sub folder of Asset/Media folder, I should be redirected to login page because authentication is not done?
I meant, can i use Auth Middleware to secure this folder? if so, Is it a valid approach if we have to access the files from a Android App?
If you want to secure files, they need to go through Laravel. Accessing the file as you do (using the full path) does not go through Laravel. You can achieve this by creating a route:
Route::group(['middleware' => ['auth']], function () {
Route::get('/secure/file/{file_name}', 'FileController#file');
}
Then, create a Controller to access the file so that you can use Auth to check for permission to access. It also means that you should put the file in an inaccessible location and use the Laravel Filesystem to access the file using PHP:
class FileController extends Controller {
public function file()
{
return Storage::get('path/to/phpunit.xml');
}
}
Laravel 5.2 has introduced HTTP Middleware, i would advise you to do it.
https://laravel.com/docs/5.2/middleware#middleware-groups
this thread might help you to get it to work...
Laravel 5.2 Auth not Working
Use the route below for it:
Route::get('/myapp/public/Asset/Media/{id}', function ($id) {
if (Auth::guest()){
return Redirect::guest('login');
}else{
$img="/myapp/public/Asset/Media/".$id;
if(File::exists($img)) {
return Response::make($img, 200, array('content-type' => 'image/jpg'));
}else{
return false;
}
})->where('id', '.+');
My sample url is here:
http://domainname.com/storage/Asset/Media/1/filename.txt
My route
Route::get('/storage/Asset/Media/{ID}/{file}', array(
'as' => 'Files',
'uses' => 'User\Account\Media\MediaController#DownloadMedia',
));
Controller Action Method
public function DownloadMedia($ID) {
$headers = array(
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename=somefile.txt"'
);
return response()->download(base_path("storage/Asset/Media/1/somefile.txt"));
}
Here important thing is I can use application/octet-stream to download any file type.
File in public folder will be accessible to everyone beacause of rewrite rules used by Laravel, Laravel won't even be called when someone access a file in the public folder.
So, you must put your restricted files somewhere else, maybe in storage folder but ultimately it doesn't matter.
After putting all your Asset/Media folder into the storage folder and updating your code who create your folder on the fly (How storage works).
Create a FileController :
PHP
class FileController extends Controller {
public function __construct() {
$this->middleware('auth');
}
public function downloadFile($filename) {
return response()->download(storage_path($filename), null, [], null);
}
}
The configure this route :
Route::get('file/{filename}', 'FileController#downloadFile')->where('filename', '^[^/]+$');
That's it, now only your authenticated user would be able to download asset files thanx to the middleware auth, that will also work for android app.

Middleware for checking if resource is owned by user

I'm having some trouble making middleware that checks if the user owns the resource being requested.
For example, if the user goes to /playlists/1/edit, and they do not own playlist 1, it should display a 401 error.
Here's what I have so far:
class CheckOwnership {
public function handle(Request $request, Closure $next)
{
if (Playlist::find($request->route()->parameters()['playlists'])->user_id !== $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
}
This is terrible and only works for the Playlist resource, but I can't find any better way of doing this.
Thanks
This can easily be achieved with the newly added Form Request Validation.
You can see it in detail here (Authorizing Form Requests):
http://laravel.com/docs/5.0/validation#form-request-validation
The example given is actually about a user attempting to edit a comment they own.
Extract:
The form request class also contains an authorize method. Within this
method, you may check if the authenticated user actually has the
authority to update a given resource. For example, if a user is
attempting to update a blog post comment, do they actually own that
comment?
In your case, simply return false from the authorize method if they do no own the Playlist.
Currently, Laravel 5 does not support passing parameters to middlewares. I use sessions instead.
On your playlist controller, fetch the owner of the playlist and store it on a session. Let's say you have a playlists table with columns userID and playlistID.
public function __construct($playlistID){
$owner = Playlist::where('playlistID',$playlistID)->pluck('userID');
Session::put('OWNER',$owner);
$this->middleware('CheckOwnership',['only'=>'edit']); // apply it to edit function only, assuming you are using a route resource
}
Then, simply retrieve it on your middleware handle function.
public function handle(Request $request, Closure $next)
{
if (Session::get('OWNER') != $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
So far, this is a simple workaround. We have to wait till Otwell considers filter-like middlewares. Hope this helps!
For those using Laravel 8, I recommend using using Policies. This would let you organize authorization logic for specific models (e.x. the Playlist model for #ntzm).
So for example, a PlaylistPolicy class can be generated,
php artisan make:policy PlaylistPolicy --model=Playlist
and then the update function could look like this.
public function update(User $user, Playlist $playlist)
{
return $user->id === $playlist->user_id;
}
There are multiple way of enforcing this policy. If you would like to use middleware, Laravel has the can middleware that can enforce policies, so new middleware won't need to be written. In your route file this would look something like this,
Route::put('playlists/{playlist}/edit', ...)
->middleware(['can:update,playlist']);
Note: If the --model option isn't used, the policy will have to be registered manually, and example policy methods won't be automatically generated.

Categories