Sunday, February 13, 2022

[FIXED] CakePHP 3 - ownership authorisation for associated tables

Issue

In the CakePHP 3 Blog Tutorial, users are conditionally authorized to use actions like edit and delete based on ownership with the following code:

public function isAuthorized($user)
{
    // All registered users can add articles
    if ($this->request->getParam('action') === 'add') {
        return true;
    }

    // The owner of an article can edit and delete it
    if (in_array($this->request->getParam('action'), ['edit', 'delete'])) {
        $articleId = (int)$this->request->getParam('pass.0');
        if ($this->Articles->isOwnedBy($articleId, $user['id'])) {
            return true;
        }
    }

    return parent::isAuthorized($user);
}

public function isOwnedBy($articleId, $userId)
{
    return $this->exists(['id' => $articleId, 'user_id' => $userId]);
}

I've been attempting to implement something similar for my own tables. For example, I have a Payments table, which is linked to Users through several different tables as follows:

  • Users->Customers->Bookings->Payments.

Foreign keys for each:

  • user_id in Customers table = Users->id (User hasOne Customer)
  • customer_id in Bookings table = Customers->id (Customer hasMany Bookings)
  • booking_id in Payments table = Bookings->id(Booking hasMany Payments)

My AppController's initialize function:

public function initialize()
    {
        parent::initialize();

        $this->loadComponent('RequestHandler');
        $this->loadComponent('Flash');
        $this->loadComponent('Auth',[
            'authorize' => 'Controller',
        ]);

        $this->Auth->allow(['display']); //primarily for PagesController, all other actions across the various controllers deny access by default
    }

In my PaymentsController, I have the following

public function initialize()
    {
        parent::initialize(); 
    }

public function isAuthorized($user)
    {        
        if (in_array($this->request->action,['view', 'edit', 'index', 'add']
            return (bool)($user['role_id'] === 1); //admin functions
        }

        if (in_array($this->request->action,['cart'])) {
            return (bool)($user['role_id'] === 2) //customer function
        }

        if (in_array($this->request->action, ['cart'])) {
            $bookingId = (int)$this->request->getParam('pass.0');
            if ($this->Payments->isOwnedBy($bookingId, $user['id'])) {
                return true;
            }
        }

        return parent::isAuthorized($user);
    }

    public function isOwnedBy($bookingId, $userId)
    {
        return $this->exists(['id' => $bookingId, 'user_id' => $userId]);
    }

I'm unsure as to how to link through the different tables to determine ownership.

  • Currently if a customer who is paying for Booking #123 could just change the URL so they are paying for Booking #111, provided that Booking exists in the database.
  • Additionally, the Booking ID is passed to the Cart function (since customers are paying for a specific booking). For example: If customer is paying for Booking #123, then the URL = localhost/project/payments/cart/123. Upon submitting their cart, a new Payment entry is created.

Also, regarding the getParam and isOwnedBy methods, hovering over them in my editor shows this:

  • Method 'getParam' not found in \Cake\Network\Request
  • Method 'isOwnedBy' not found in App\Model\Table\PaymentsTable

However, I've gone through the entire BlogTutorial and can't find anywhere else that getParam or isOwnedBy is used or set up in the Model.


Solution

In the IsAuthorized function in PaymentsController:

if (in_array($this->request->action, ['cart'])) {
    $id = $this->request->getParam('pass'); //use $this->request->param('pass') for CakePHP 3.3.x and below.
    $booking = $this->Payments->Bookings->get($id,[
        'contain' => ['Artists']
    ]);
    if ($booking->artist->user_id == $user['id']) {
        return true;
    }
}


Answered By - mistaq

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.