Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cakePHP 3 best practises for using request/auth data in model (account based cloud service)

I'm creating a cloud service where there are multiple accounts and each account has multiple users. Users can only log into their account. Each account has an unique alias. (e.g. abc). If users go to their sub-domain (e.g. abc.cloud.service) they are able to log into their account and see only the stuff which is assigned to that account. The login works very well.

I have a htaccess redirection rule added which transforms the sub-domain to a query.

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{HTTP_HOST} ^([a-z0-9]+)\.cloud\.service$ [NC]
RewriteRule ^ index.php?alias=%1 [L]

So in all controllers I have the alias as a query available. In order to guarantee that users are only allowed to see their contents all models are related to an account. Every time I want to perform a find operation on one of those models I want to make sure that the results belong to the correct account. I'm doing that in the beforeFind.

// beforeFind of models which are related to an account
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary) {
    if ($primary) {
        $query->contain(['Accounts']);
    }
}

And to get the correct account I have a beforeFind in the Accounts model where I check the query.

// beforeFind of the Accounts model
public function beforeFind (Event $event, Query $query, ArrayObject $options, $primary) {
    if (isset($_GET['alias'])) {
        $query->where(['Accounts.alias' => $_GET['alias']]);
    }
}

This works amazingly well as all my find operations are returning only data which is allowed for the that account. But I know what I'm doing is probably not called best practice. Question 1: So is there a better way of doing that? I don't know how to access the query data in the model?

Problem 2: When I save any type of content I need to make sure it is assigned to the correct account.

Option 1 (ineffective due to too many occurrences) I could manually assign the accound id before all save calls depending on the users account id. But I'm going to have too many save calls, so I would like to have a better solution here.

Option 2 (is what I'm doing right now) I add some code to the beforeSave to all models which are related to the account:

// beforeSave in models related to Accounts model
public function beforeSave(Event $event, Entity $entity, ArrayObject $options)
{
    if (!isset($_SESSION['Auth']['User']['account_id'])) {
        $entity->account_id = $_SESSION['Auth']['User']['account_id'];
    }
}

Works again very well, but I also know that accessing the session variable directly is not recommended by cakePHP. Question 2, is there a better solution here? I didn't find a working solution on accessing auth data in models

like image 554
Andreas Daoutis Avatar asked Feb 10 '26 05:02

Andreas Daoutis


2 Answers

It sounds like what you're creating is what's known as a 'multi-tenant' application.

There is a plugin for CakePHP 3 in development that will handle this for you, and is very similar to the method you've got implemented, but with some extra bells and whistles built in. The plugin uses the subdomain as the query (then subsequent foreign_key of 'account_id' on all tables, so you don't have to append the alias within .htaccess and use the global _GET like you're currently doing.

See it here: https://github.com/pronique/multitenant

like image 176
wilsmex Avatar answered Feb 13 '26 10:02

wilsmex


First of all, I would say accessing authorization information within your models is not best practice because it doesn't enforce a good level of separation of concerns between controller logic and business logic.

I would say the best case to use here would be a dispatch filter, perhaps something like:

class SubdomainFilter extends DispatcherFilter
{
    public function beforeFilter(Event $event)
    {
        if (isset($_GET['alias'])) {
            $event->data['request']->subdomain_alias = $_GET['alias'];
        }
    }
}

Then initialize your new filter in bootstrap.php:

DispatchFactory::add('SubdomainFilter');

Then finally you can access the subdomain alias as a property attached to your request object in your controller to be passed as an argument into your model functions:

class FooController extends AppController
{
    public function exampleEndpoint() {
        $your_table->findFunction($this->request->subdomain_alias);
        $your_table->saveFunction($this->request->subdomain_alias, $data);
}

You can read more about Dispatch Filters in CakePHP 3 here.

like image 24
Wes King Avatar answered Feb 13 '26 10:02

Wes King



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!