How authorize for multiple user on Policies in laravel? - authorization

Hello i'm trying to authorize 2 differents users to view one of my pages.
On the Policy's view function i passed my two users, and it doesn't worked.
I've tried many ways.
public function view(User $user, Proposal $proposal)
{
return $user->id === $proposal->user_id;
return $user->id === $proposal->annonce->user->id;
}
public function view(User $user, Proposal $proposal)
{
return $user->id === $proposal->user_id; $proposal->annonce->user->id;
}
public function view(User $user, Proposal $proposal)
{
$good_user = $proposal->user_id; $proposal->annonce->user->id;
return $user->id === $good_user;
}
Problem is, in my view the #can(show, $proposal), only authorize the first user i've write on the policy, never the second. Have you got some ideas i could try?

I finally find an answer. I put it here, hoping, one day, it will help someone.
if($user->id === $proposal->user_id) {
return true;
} elseif($user->id === $proposal->annonce->user->id) {
return true;
}
Don't know if it's the best way, but it works ^^

Related

Dynamic domain in reset password link Laravel 8

I'm aware that password reset link can be customized by adding the below function in AuthServiceProvider.php
ResetPassword::createUrlUsing(function ($user, string $token) {
return 'https://example.com/reset-password?token='.$token;
});
This is my sendResetPassword function
public function sendResetPassword(Request $request) {
$request->validate(['email' => 'required|email']);
$status = Password::sendResetLink(
$request->only('email')
);
if ($status === Password::RESET_LINK_SENT) {
return response()->json(['message' => __($status)], 200);
} else {
return response()->json(['message' => __($status)], 500);
}
}
Now I'm wondering if there is a way to pass a domain from the sendResetPassword $request to the createUrlUsing function.
The main purpose of this is to avoid hardcoding the frontend URL in my API. I just want that the forgot password form in my frontend sends the email and also the domain.
Not sure if this is the best approach, but as soon I posted the question I found that this is a working solution:
ResetPassword::createUrlUsing(function ($user, string $token) {
return $this->app->request->headers->get('origin').'/reset-password?token='.$token;
});

Laravel8 role based access using and pivot tables and middlewares

I am trying to write a large project using laravel8 and will need role based access. So I have created a roles table and linked it to the users table via a modal table called role_user. Now I am able to create the access and perform checks inside controllers and users model and everything works correctly
my problem is that this way I have to keep checcking if a user has access in each and every function for all my user access levels and this is tedious.
I have thus tried to convert this approcah and use middlewares but the problem is that I am unable to redirect the users to the appropriate dashboard upon authentication and they are both being redirected to the home page that is designated for users only.
I have tried the following
I have addes the following code to the users model to create a many to many relationship between the users and the roles and also to check if the user has roles and peform appropriate tasks as per the code blocks
public function roles() {
return $this->belongsToMany(Role::class);
}
public function checkRoles($roles) {
if ( ! is_array($roles)) {
$roles = [$roles];
return false;
}
if ( ! $this->hasAnyRole($roles)) {
auth()->logout();
abort(404);
}
}
public function hasAnyRole($roles): bool {
return (bool) $this->roles()->whereIn('name', $roles)->first();
}
public function hasRole($role): bool {
return (bool) $this->roles()->where('name', $role)->first();
}
Now this would have worked if I proceeded to peforem checks on each controller's model directly but after creating middlewares for each role and peforming checks in each as follows things failled
a) Admin Middleware
public function handle(Request $request, Closure $next){
if (Auth::user() && $request->user()->hasRole('admin')) {
return $next($request);
}
return redirect('home')->with('error','You have not admin access');
}
b) SuperAdmin Middleware
public function handle(Request $request, Closure $next) {
if (Auth::user() && $request->user()->hasRole('super_admin')) {
return $next($request);
}
return redirect('home')->with('error','You have not permission to peform this task');
}
Now after registering all the middlewareds inside the Kernel class, I modified the login Controller class and added the following code inside the construct function:
if(Auth::check()){
if(Auth::user()->hasRole('super_admin')){
return redirect(RouteServiceProvider::SUPERADMINADMINHOME);
}elseif(Auth::user()->hasRole('admin')){
return redirect(RouteServiceProvider::ADMINHOME);
}elseif(Auth::user()->hasRole('vendor')){
return redirect(RouteServiceProvider::VENDORHOME);
}else {
return redirect(RouteServiceProvider::HOME);
}
}
I also updated my routes as follows
Route::group(['prefix' => 'super', 'as' => 'super.', 'namespace' => 'Super', 'middleware' => ['Super']], function () {
Route::get('/', [App\Http\Controllers\SuperAdminController::class,'index'])->name('superadminhome');
});
Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin', 'middleware' => ['Admin']], function () {
Route::get('/', [App\Http\Controllers\AdminController::class,'index'])->name('adminhome');
});
Route::group(['prefix' => 'vendor', 'as' => 'vendor.', 'namespace' => 'Vendor', 'middleware' => ['Vendor']], function () {
Route::get('/', [App\Http\Controllers\VendorController::class,'index'])->name('vendorhome');
});
After doing all this, I tried tom login with the superadmin credentials and the admin credentials but all of them take me to the same page home. What could I be doing wrong or where can I get step by step guide to how to achieve this task noting that I am new to middlewares in laravel.
From what I can see you are having this problem becasue the checks are all yielding false hence the last is being taken into account. That would mean that the way in which you are checking for the roles is probably wrong. I would suggest looping through the roles and using a switch statement. checking if it matches the ones you need and then redirecting appropriately.
This would mean changing your if statements that check for the availability of a specific roles.
That is this part of your code
if(Auth::user()->hasRole('super_admin')){
return redirect(RouteServiceProvider::SUPERADMINADMINHOME);
}elseif(Auth::user()->hasRole('admin')){
return redirect(RouteServiceProvider::ADMINHOME);
}elseif(Auth::user()->hasRole('vendor')){
return redirect(RouteServiceProvider::VENDORHOME);
}else {
return redirect(RouteServiceProvider::HOME);
}

How to add captcha required only for a particular condition yii2

I am trying make the captcha field required only when the number of failed login attempts exceed 3 times. For which I have written below code till now.
In LoginForm model I have added the below rules
public function rules()
{
return [
[['username', 'password'], 'required'],
['password', 'validatePassword'],
['verifyCode', 'captcha', 'when' => function($model) {
return $this->checkattempts();
}],
];
}
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addLoginAttempt($user->id);
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
public function checkattempts()
{
$user = $this->getUser();
$ip = $this->get_client_ip();
$data = (new Query())->select('*')
->from('login_attempts')
->where(['ip' => $ip])->andWhere(['user_ref_id' => $user->id])
->one();
if($data["attempts"] >=3){
return false;
}else{
return true;
}
}
public function addLoginAttempt($uid) {
$ip = $this->get_client_ip();
$data = (new Query())->select('*')
->from('login_attempts')
->where(['ip' => $ip])->andWhere(['user_ref_id' => $uid])
->one();
if($data)
{
$attempts = $data["attempts"]+1;
#Yii::$app->db->createCommand("UPDATE login_attempts SET attempts=".$attempts." where ip = '$ip' AND user_ref_id=$uid")->execute();
}
else {
Yii::$app->db->createCommand("INSERT into login_attempts (attempts, user_ref_id, ip) VALUES(1,'$uid', '$ip')")->execute();
}
}
Here I am validating the password first. If the password is incorrect then I am incrementing the count by 1. This part is working fine. The count is incrementing successfully.
After this I am trying to get the count of failed attempts while validating captcha using the function checkattempts(), but it is not working.
Can anyone please tell me where I have made mistake.
Thanks in advance.
In your model:
if (!$model->checkattempts())
//show the captcha
Then, in your model rules you'll need something like:
['captcha', 'captcha'],
In your case, what you can do is use different scenarios depending on the user attempts, and in one scenario (more than X attempts) make the captcha required.
More documentation about the captcha and about scenarios.

CakePHP3 BeforeFilter & Auth Redirect

Can anyone help me understand the deal with Cakephp 3.3 and a BeforeFilter/Auth Redirect issue I'm experiencing.
I'm using the default Auth component. I've created a custom component that additionally checks for a session variable (Registration), and if that variable is not set redirects to a page designed to make a selection to set the desired Registration.
Here's my custom component:
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\Network\Request;
class RegistrationCheckComponent extends Component
{
private $_allowedActions = [];
private $_superUserBypass = false;
public $components = ['Auth'];
public function superUserBypass($val = false) {
$this->_superUserBypass = $val;
}
public function allow(Array $allowedActions = []) {
$this->_allowedActions = $allowedActions;
}
public function verify() {
if($this->_superUserBypass) {
return true;
}
$session = $this->request->session();
//if Auth Registration is not set
if(!$session->read('Auth.Registration')) {
//if requested action is not in the array of allowed actions, redirect to select registration
if(!in_array($this->request->param('action'), $this->_allowedActions)) {
return $this->redirect();
};
return true;
}
return true;
}
public function redirect() {
$controller = $this->_registry->getController();
return $controller->redirect($this->config('redirect'));
}
}
Not all controller's require the Registration variable to be set, that's why I decided to go with the component approach. The component is however loaded in the AppController by this line:
$this->loadComponent('RegistrationCheck', ['redirect' => ['controller' => 'Users', 'action' => 'registrations']]);
In the controllers that require the Registration variable to be set, I include the following beforeFilter function:
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
return $this->RegistrationCheck->verify();
}
Now, I've had some Integration Tests defined, here's one of them:
public function testUnauthenticatedEdit()
{
$this->get('/teams/edit');
$this->assertRedirect(['controller' => 'Users', 'action' => 'login']);
}
So, after I implemented my RegistrationCheck component, I ran the Integration Tests. I was expecting the test to pass, it did not. The interesting thing is that it actually returned a redirect to Users->registrations rather than Users->login as I had expected.
It looks to me that the RegistrationCheck redirect is happening before the Auth component redirect. I'm not sure it's a huge deal, because a redirect to registrations without Auth set is going to end up redirecting back to login, but it seems incorrect to ignore it...also, I'd just like to understand a bit more of what is actually going on.
Can anyone suggest changes to my code that would ensure the Auth component is handled before the RegistrationCheck component?
Thanks in advance.
Well, after a little more research, I found the answer I'm looking for here: http://book.cakephp.org/3.0/en/controllers/components/authentication.html#deciding-when-to-run-authentication
Pretty simple really, just wanted to include an answer here for anyone who may stumble across the same question.

Is my order complete return approach correct?

When a customer is returned to the following URL (example);
http://prestashop.dev/index.php?action=completed&controller=callback&fc=module&hmac={valid-hmac}&merchant_order_id=14&module=chippin
After a successful payment, It will call on this FrontController sub-class;
class ChippinCallbackModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
$chippin = new Chippin();
$payment_response = new PaymentResponse();
$payment_response->getPostData();
// if a valid response from gateway
if(ChippinValidator::isValidHmac($payment_response)) {
// "action" is passed as a param in the URL. don't worry, the Hmac can tell if it's valid or not.
if ($payment_response->getAction() === "completed") {
// payment_response->getMerchantOrderId() will just return the id_order from the orders table
$order_id = Order::getOrderByCartId((int) ($payment_response->getMerchantOrderId()));
$order = new Order($order_id);
// this will update the order status for the benefit of the merchant.
$order->setCurrentState(Configuration::get('CP_OS_PAYMENT_COMPLETED'));
// assign variables to smarty (copied this from another gateway, don't really understand smarty)
$this->context->smarty->assign(
array(
'order' => $order->reference,
)
);
// display this template
$this->setTemplate('confirmation.tpl');
I'm quite new to Prestashop. I'm just not sure if this is technically done or not. The confirmation.tlp view does display with the order->reference and the order status is updated to "Completed" but is this all I need?
Are there any other considerations? I have the opportunity to call a hookDisplayPaymentReturn at this point but why should I?
I seem to have a pretty standard return page. Is this enough;
Update - Do I just call a hook something like;
public function displayPaymentReturn()
{
$params = $this->displayHook();
if ($params && is_array($params)) {
return Hook::exec('displayPaymentReturn', $params, (int) $this->module->id);
}
return false;
}
As far as I can see everything seems okay for me.
You should consider adding hookDisplayPaymentReturn it allows other modules to add code to your confirmation page. For example a Google module could add javascript code that sends order informations to analytics on confirmation page.
EDIT
class ChippinCallbackModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
$chippin = new Chippin();
$payment_response = new PaymentResponse();
$payment_response->getPostData();
// if a valid response from gateway
if(ChippinValidator::isValidHmac($payment_response)) {
// "action" is passed as a param in the URL. don't worry, the Hmac can tell if it's valid or not.
if ($payment_response->getAction() === "completed") {
// payment_response->getMerchantOrderId() will just return the id_order from the orders table
$order_id = Order::getOrderByCartId((int) ($payment_response->getMerchantOrderId()));
$order = new Order($order_id);
// this will update the order status for the benefit of the merchant.
$order->setCurrentState(Configuration::get('CP_OS_PAYMENT_COMPLETED'));
// assign variables to smarty (copied this from another gateway, don't really understand smarty)
$this->context->smarty->assign(
array(
'order' => $order->reference,
'hookDisplayPaymentReturn' => Hook::exec('displayPaymentReturn', $params, (int) $this->module->id);
)
);
$cart = $this->context->cart;
$customer = new Customer($cart->id_customer);
Tools::redirect('index.php?controller=order-confirmation&id_cart='.$cart->id.'&id_module='.$this->module->id.'&id_order='.$order->id.'&key='.$customer->secure_key);
And in your module :
class myPaymentModule extends PaymentModule
{
public function install()
{
if (!parent::install() || !$this->registerHook('paymentReturn'))
return false;
return true;
}
// Example taken from bankwire module
public function hookPaymentReturn($params)
{
$state = $params['objOrder']->getCurrentState();
$this->smarty->assign(array(
'total_to_pay' => Tools::displayPrice($params['total_to_pay'], $params['currencyObj'], false),
'bankwireDetails' => Tools::nl2br($this->details),
'bankwireAddress' => Tools::nl2br($this->address),
'bankwireOwner' => $this->owner,
'status' => 'ok',
'id_order' => $params['objOrder']->id
));
if (isset($params['objOrder']->reference) && !empty($params['objOrder']->reference))
$this->smarty->assign('reference', $params['objOrder']->reference);
return $this->display(__FILE__, 'confirmation.tpl');
}
}