Laravel restrict content based on customer/user/tenant

Viewed 647

I'm in a dilemma to find some sort of logic to restrict user access to content within the same model. For example, a supplier only can see products that they supply and the customer only can see a product which they buying. (note: Each product can have multiple supplier or customers. We call id a product node)

Now, I have the relationship set to a product belongs to many suppliers and a product belongs to multiple customers.

Currently I have the spatie roles and permissions in my site, which works great for 1 tenant (mainly our office(50-150 users)). It is not a problem if our office user can see details of multiple customers or products, but the problem starts when the customer logs in. I only want to show the product pricing or data that belonging to them. It is a big no no to see any other customer or supplier data.

I looked multi tenancy implementation, but I believe this wouldn't cover my need.

I apologise if I've overlooked something, but I try to keep the data as secured as possible.

Could you please shed some light on this dilemma and direct me to the correct path?

Many thanks for your input!

1 Answers

I'm assuming that all of the models you want to restrict have a relationship directly to the customer, so you can actually add a global scope that adds a default parameter to the query.

Take the following scope:

<?php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;

class CustomerOwnedScope implements Scope {
    public function apply(Builder $builder, Model $model) {
        if (Auth::guard('customer')->check()) {
            $builder->where('customer_id', '=', Auth::guard('customer')->id);
        }
    }

    public function extend(Builder $builder) {
        $this->addWithoutCustomer($builder);
    }

    protected function addWithoutCustomer(Builder $builder) {
        $builder->macro('withoutCustomer', function (Builder $builder) {
            return $builder->withoutGlobalScope($this);
        });
    }
}

Any model that has this scope will automatically add the clause WHERE customer_id = ? where ? is the id of the currently authenticated customer, if one is authenticated. Assuming that you're using Laravel auth this would prevent you from having to do anything specific to achieve your goal.

It also adds the scope withoutCustomer() that would prevent the where clause from being added.

The simplest way to add this to a model that belongs to a customer would be to create yourself a trait (concern) like so:

<?php
namespace App\Concerns;

use App\Scopes\CustomerOwnedScope;

trait OwnedByCustomer {
    public static function bootOwnedByCustomer() {
        static::addGlobalScope(new CustomerOwnedScope);
    }

    public function customer() {
        $this->belongsTo(Customer::class, 'customer_id');
    }
}

This would add the customer relationship as well as add the scope to automatically query based on the current customer.

You can obviously modify this further to include other relationships, or you can add some more conditions to only apply for customers with a certain flag set, or not set (for internal users, etc).

This whole approach does assume that your internal admin users and your external customer users are using different auth guards (which would be the ideal approach in this situation).

I should add that the code above is taken from an article I wrote on the subject of multi-tenancy, specifically the part about dealing with tenants in a single database. If you would like, you can read it here: https://ollieread.com/articles/laravel-multi-tenancy-avoiding-over-engineering#single-database

Related