Laravel Gates and/or Policies - php

Right now Im thinking about implementing policies into my Project.
So I want 3 types of roles: User, Admin and Office.
I have a Customer class which has the following CRUD functions:
class CustomerController extends Controller
{
public function getAllCustomer()
{
return Customer::get();
}
public function addNewCustomer(Request $request)
{
$validatedData = $request->validate([
'Title' => 'required',
'Name' => 'required|max:255',
'Surname' => 'required|max:255',
'Email' => 'required',
'Phone' => 'required',
'Password' => 'required',
'dateofBirth' => 'required'
]);
return \app\model\Customer::create($request->all());
}
public function update (Request $request , Customer $id)
{
$id->update($request->all());
}
public function destroy (Customer $id)
{
$id->delete();
}
What I want is that only the Admin should be able to use the CRUD functions for all the Customers. A User should only be able to use CRUD functions on his own data (so the Customer can only do it with his own data).
I read this article:
https://code.tutsplus.com/tutorials/gates-and-policies-in-laravel--cms-29780
But I still don't know exactly how to implement it in my Project, and whether I should use Gates or policies (it seems like policies are easier, or?).
I would be very thankful if somebody could write me an example on how to implement it on my work.

Create policies for each of your model. Bind policies to routes https://laravel.com/docs/5.6/authorization#via-middleware
Route::post('/post/{post}', function () {
// The current user may edit post...
})->middleware('can:edit,post');
Create a parent policy which all the other policies will extend and add to it before method which will basically check if your user is admin and if so no further checking is needed.
public function before(User $authenticatedUser, $ability)
{
// super admin can access everything
if ($authenticatedUser->role == 'super_admin') {
return true;
}
return null; //fall through to the policy method
}
Create a Post policy which will have edit permission check
class PostPolicy extends Policy
{
public function edit(User $authenticatedUser, Post $post)
{
return $authenticatedUser->id == $post->author_id;
}
}
The general idea, on how to work with policies is as simple as that.

Related

Move logic from Controller to Service class

I'm working on a simple login page which works already, but I wanna experiment with Service classes and how they work.
How would I be able to properly move my logic from my controller to a service class, and call that service class in my controller?
AuthController:
public function postLogin(Request $request)
{
$request->validate([
'email' => 'required',
'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
return redirect()->intended('dashboard')
->withSuccess('You have successfully logged in');
}
return redirect("login")
->withSuccess('Oops! You have entered invalid credentials'); // MIGHT CHANGE TO withSuccess
}
I've made a UserService.php file under App\Services
I've tried pasting the function into my Service class, then calling it in the controller with
public function postLogin(Request $request)
{
(new UserService())->postLogin($request->validated());
}

How to override the validation rule login Laravel\Fortify?

This described in the class Laravel\Fortify\Http\Requests\LoginRequest
I want to add one more validation line
namespace Laravel\Fortify\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Laravel\Fortify\Fortify;
class LoginRequest extends FormRequest
{
/..
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
];
}
}
But I cannot do the customizations in the vendor.
My question is how to do this?
Just to add to the comment above, it's a bad idea to make changes in the vendor folder as stated earlier. For one, any code pushes to a repository will not reflect these changes (unless you modify the ignore file).
For Laravel/Fortify adding new fields and changing the default validation rules, even the password requirements is very straightforward. It's not clear to me what your requirement is, but it might be easier to simply use a Validator. That is what Fortify uses as well. For example, Fortify publishes two files:
App\Actions\Fortify\CreateNewUser.php
App\Actions\Fortify\PasswordValidationRules.php
To add a new validation rule for a field, simply add it the CreateNewUser.php under the Validator::make method that Fortify itself is using. You can follow the same logic in your custom implementation. For example to add a firstname field, modify as follows:
Validator::make($input, [
'firstname' => ['required', 'string', 'max:255'],
])->validate();
return User::create([
'firstname' => $input['firstname'],
]);
You can add as many field as you want. To change the password requirements, make changes to the passwordRules() function in the PasswordValidationRules.php file as follows:
protected function passwordRules()
{
return ['required',
'string',
(new Password)->requireUppercase()
->length(10)
->requireNumeric()
->requireSpecialCharacter(),
'confirmed'];
}
All this info can be found at the official docs https://jetstream.laravel.com/1.x/features/authentication.html#password-validation-rules
In short, I solved the problem like this
copy vendor\laravel\fortify\src\Http\Controllers\AuthenticatedSessionController.php to
app\Http\Controllers\Auth\LoginController.php (change namespace and class name)
copy vendor\laravel\fortify\src\Http\Requests\LoginRequest.php to app\Http\Requests\LoginFormRequest.php (change namespace and class name)
add new route in routes/web.php
use App\Http\Controllers\Auth\LoginController;
//
Route::post('/login', [LoginController::class, 'store'])
->middleware(array_filter([
'guest',
$limiter ? 'throttle:'.$limiter : null,
]));
in LoginController changed LoginRequest to LoginFormRequest and
public function store(LoginFormRequest $request)
{
return $this->loginPipeline($request)->then(function ($request) {
return app(LoginResponse::class);
});
}
in LoginFormRequest add my new rule(s)
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
'myNewRule' => 'required|string',
];
}
Here's how you can use your own validation rules when authenticating with Fortify:
Create a file LoginRequest.php in App\Http\Requests that extends Fortify's LoginRequest.php class
I would just copy that class and update the namespace:
namespace App\Http\Requests;
use Laravel\Fortify\Http\Requests\LoginRequest as FortifyLoginRequest;
use Laravel\Fortify\Fortify;
class LoginRequest extends FortifyLoginRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
'myAttribute' => 'rules' // Customizations...
];
}
}
Add a binding in your AppServiceProviders.php boot method to resolve Fortify's class with your own:
public function boot()
{
$this->app->bind('Laravel\Fortify\Http\Requests\LoginRequest', \App\Http\Requests\LoginRequest::class);
}
I've found that page: https://dev.to/aibnuhibban/login-customization-in-laravel-8-2gc8
Go to vendor > laravel > fortify > src > Rule > Password.php
There you can change those config.

Is it possible to override injected Request from a Laravel Package?

I'm building a custom package in Laravel that has a resource Controller. For this example the resource is an Organization In this Controller the basic index, show, store etc actions are defined.
This is my store method in the controller:
/**
* Store a newly created Organization in storage.
*
* #param OrganizationStoreRequest $request
* #return JsonResponse
*/
public function store($request): JsonResponse
{
$newOrganization = new Organization($request->all());
if ($newOrganization->save()) {
return response()->json($newOrganization, 201);
}
return response()->json([
'message' => trans('organilations::organisation.store.error')
], 500);
}
My OrganzationStoreRequest is pretty basic for now:
class OrganizationStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => 'required'
];
}
}
The store method can be called on a API When the package is being used in a Laravel application. My problem here is that I want to give the users of my package the ability to override my OrganizationStoreRequest with there own requests, as they might have to use different authorize or validation methods.
I've tried building a middleware and Binding my own instance to the OrganizationStoreRequests but I'm not getting the results I want.
Is there anyway for the user of the package to be able to override the OrganizationStoreRequets in the controller of my package ?
With the help of fellow developers on Larachat we've come to the following (easy) solution.
When creating an own request we can bind an instance of it to the IoC Container. For example, creating the OrganizationOverrideRule and extending the original:
class OrganizationOverrideRules extends OrganizationStoreRequest
{
public function rules(): array
{
return [
'name' => 'required',
'website' => 'required',
'tradename' => 'required'
];
}
}
Then in the AppServiceProvider you can bind the new instance like so:
App::bind(
OrganizationStoreRequest::class,
OrganizationOverrideRules::class
);
Then the OrganizationOverrideRules will be used for validation and authorization.

How to validate and create on laravel 5.2 on controller?

Hello I'm trying to create a code generator to invite a user from input email, I want to save on the database the user id who send the invite, the code, and the email who is going to recive the invite, but I can't get the id of my auth user doing $request->user('id') (not working) also I know there is other method to do this easier than using DB::table something like
$request->user()->invites()->create... my controller looks like
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use DB;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class invitacionController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index(Request $request)
{
return view('administrar');
}
public function invitacion(Request $request)
{
$this->validate($request, [
'email' => 'required|email|max:255|unique:invites',
]);
/*$request->user()->invites()->create([
'code' => str_random(40),
]);*/
DB::table('invites')->insert([
['usuId' => $request->user('id'), 'code' => str_random(40), 'email' => $request->input('email')],
]);
return redirect('/administrar');
}
}
If the relationship is properly configured, the first (commented) method should be working.
As for the second method, I think you are adding extra brackets:
DB::table('invites')->insert([
'usuId' => $request->user()->id, // <---
'code' => str_random(40),
'email' => $request->input('email')
]);
That hasMany method can take additional parameters. By default it expects the column to be named user_id, but you called it usuId. The documentation gives this example:
return $this->hasMany('App\Comment', 'foreign_key');
So I think you should do
public function invites()
{
return $this->hasMany(Invite::class, 'usuId');
}

Laravel 4 - Hardcoded authentication

I want to create authentication mechanism without need for database where only one person (admin) who knows right username and password (which I would hardcode) would be able to login. I still want to use Auth::attempt(), Auth::check() and other functions.
I found out that I could create my own User driver, but it seems to me that there should be something simpler.
Maybe it is not very nice solution, but I want as simple as possible website.
It may only seem there should be something simpler, but in fact that's as simple as you can get if you want to extend the authentication system. All the methods you're using through the Auth facade (like attempt, check, etc.), are implemented within the Illuminate\Auth\Guard class. This class needs a UserProviderInterface implementation to be injected into the constructor in order to work. Which means that in order to use the Auth facade you either need to use the already implemented DatabaseUserProvider or EloquentUserProvider, or implement your own provider that handles the simple login you want.
Although the article you linked to may look lengthy, to achieve what you need you might get away with much less code in the provider than you might think. Here's what I think is what you need:
1. In your app/config/auth.php change the driver to simple and append the desired login credentials:
'driver' => 'simple',
'credentials' => array(
'email' => 'user#email.com',
'password' => 'yourpassword'
)
2. Create a file in your app directory called SimpleUserProvider.php that has this code:
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\GenericUser;
use Illuminate\Auth\UserProviderInterface;
class SimpleUserProvider implements UserProviderInterface {
protected $user;
public function __construct(array $credentials)
{
$this->user = new GenericUser(array_merge($credentials, array('id' => null)));
}
// If you only need to login via credentials the following 3 methods
// don't need to be implemented, they just need to be defined
public function retrieveById($identifier) { }
public function retrieveByToken($identifier, $token) { }
public function updateRememberToken(UserInterface $user, $token) { }
public function retrieveByCredentials(array $credentials)
{
return $this->user;
}
public function validateCredentials(UserInterface $user, array $credentials)
{
return $credentials['email'] == $user->email && $credentials['password'] == $user->password;
}
}
3. Lastly you'll need to register the new provider with the authentication system. You can append this to the app/start/global.php file:
Auth::extend('simple', function($app)
{
return new SimpleUserProvider($app['config']['auth.credentials']);
});
This should give you a simple (no database) user authentication while still being able to use Laravel's facades.
The accepted answer did not work for me. Every time I logged in, the login was successful but when on the /home page, I was redirected to the login page again.
I found out that this was due to the user not being stored in the session as authenticated user. To fix this, I had to implement the getAuthIdentifier method in the User model class and also implement the retrieveById method .
I've also adjusted my solution to support multiple hard coded users (it presumes, that the email is unique, so we can also use it as id for the user):
1. In app/config/auth.php:
'providers' => [
'users' => [
'driver' => 'array',
],
],
'credentials' => [
'userA#email.com' => 'passA',
'userB#email.com' => 'passB',
]
2. The UserProvider:
use \Illuminate\Auth\GenericUser;
use \Illuminate\Contracts\Auth\UserProvider;
use \Illuminate\Contracts\Auth\Authenticatable;
class ArrayUserProvider implements UserProvider
{
private $credential_store;
public function __construct(array $credentials_array)
{
$this->credential_store = $credentials_array;
}
// IMPORTANT: Also implement this method!
public function retrieveById($identifier) {
$username = $identifier;
$password = $this->credential_store[$username];
return new User([
'email' => $username,
'password' => $password,
]);
}
public function retrieveByToken($identifier, $token) { }
public function updateRememberToken(Authenticatable $user, $token) { }
public function retrieveByCredentials(array $credentials)
{
$username = $credentials['email'];
// Check if user even exists
if (!isset($this->credential_store[$username])) {
return null;
}
$password = $this->credential_store[$username];
return new GenericUser([
'email' => $username,
'password' => $password,
'id' => null,
]);
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
return $credentials['email'] == $user->email && $credentials['password'] == $user->getAuthPassword();
}
}
3. And in app/Providers/AuthServiceProvider:
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
...
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
Auth::provider('array', function($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new ArrayUserProvider($app['config']['auth.credentials']);
});
}
}
4. In User.php (model):
class User extends Authenticatable
{
...
public function getAuthIdentifier()
{
return $this->email;
}
}
More Information:
For everyone who is interested, why there need to be the above stated additions:
When you login, the method login in Illuminate\Auth\SessionGuard is called. In this method you will find, that the identifier of the user is stored in the session with $this->updateSession($user->getAuthIdentifier()). Therefore we need to implement this method in our user model.
When you add $this->middleware('auth') in your controller, then the method authenticate() in Illuminate\Auth\Middleware\Authenticate is called. This again calls $this->auth->guard($guard)->check() to check, whether a user is authenticated. The check() method only tests, that there exists a user in the session (see Illuminate\Auth\GuardHelpers). It does this by calling the user() method of the guard, which in this case is again SessionGuard. In the user() method, the user is retrieved by taking the id stored in the session and calling retrieveById to get the user.

Categories