mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Additional permissions levels when we want to filtered and intersect permissions
This commit is contained in:
parent
daf65587ca
commit
4364b4369e
@ -62,6 +62,13 @@ class BaseController extends Controller
|
||||
*/
|
||||
public $forced_index = 'data';
|
||||
|
||||
protected $entity_type;
|
||||
|
||||
protected $entity_transformer;
|
||||
|
||||
protected $serializer;
|
||||
|
||||
private array $client_exclusion_fields = ['balance','paid_to_date', 'credit_balance','client_hash'];
|
||||
/**
|
||||
* Fractal manager.
|
||||
* @var object
|
||||
@ -187,7 +194,7 @@ class BaseController extends Controller
|
||||
* end user has the correct permissions to
|
||||
* view the includes
|
||||
*
|
||||
* @param array $includes The includes for the object
|
||||
* @param string $includes The includes for the object
|
||||
* @return string The filtered array of includes
|
||||
*/
|
||||
private function filterIncludes(string $includes): string
|
||||
@ -286,6 +293,7 @@ class BaseController extends Controller
|
||||
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
'company.company_gateways' => function ($query) use ($user) {
|
||||
$query->whereNotNull('updated_at')->with('gateway');
|
||||
@ -481,21 +489,32 @@ class BaseController extends Controller
|
||||
);
|
||||
|
||||
if ($query instanceof Builder) {
|
||||
//27-10-2022 - enforce unsigned integer
|
||||
|
||||
$limit = $this->resolveQueryLimit();
|
||||
|
||||
$paginator = $query->paginate($limit);
|
||||
|
||||
$query = $paginator->getCollection();
|
||||
|
||||
$resource = new Collection($query, $transformer, $this->entity_type);
|
||||
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
} else {
|
||||
|
||||
$resource = new Collection($query, $transformer, $this->entity_type);
|
||||
|
||||
}
|
||||
|
||||
return $this->response($this->manager->createData($resource)->toArray());
|
||||
}
|
||||
|
||||
private function resolveQueryLimit()
|
||||
|
||||
/**
|
||||
* Returns the per page limit for the query.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function resolveQueryLimit(): int
|
||||
{
|
||||
if (request()->has('per_page')) {
|
||||
return abs((int)request()->input('per_page', 20));
|
||||
@ -637,6 +656,11 @@ class BaseController extends Controller
|
||||
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
|
||||
});
|
||||
}
|
||||
|
||||
if($user->hasIntersectPermissions(['view_client'])){
|
||||
$query->exclude($this->client_exclusion_fields);
|
||||
}
|
||||
|
||||
},
|
||||
'company.company_gateways' => function ($query) use ($user) {
|
||||
$query->whereNotNull('created_at')->with('gateway');
|
||||
@ -847,23 +871,32 @@ class BaseController extends Controller
|
||||
$query->with($includes);
|
||||
|
||||
if (auth()->user() && ! auth()->user()->hasPermission('view_'.Str::snake(class_basename($this->entity_type)))) {
|
||||
//06-10-2022 - some entities do not have assigned_user_id - this becomes an issue when we have a large company and low permission users
|
||||
|
||||
if (in_array($this->entity_type, [User::class])) {
|
||||
|
||||
$query->where('id', auth()->user()->id);
|
||||
|
||||
} elseif (in_array($this->entity_type, [BankTransactionRule::class,CompanyGateway::class, TaxRate::class, BankIntegration::class, Scheduler::class, BankTransaction::class, Webhook::class, ExpenseCategory::class])) { //table without assigned_user_id
|
||||
|
||||
if ($this->entity_type == BankIntegration::class && !auth()->user()->isSuperUser() && auth()->user()->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) {
|
||||
$query->exclude(["balance"]);
|
||||
} //allows us to selective display bank integrations back to the user if they can view / create bank transactions but without the bank balance being present in the response
|
||||
else {
|
||||
$query->where('user_id', '=', auth()->user()->id);
|
||||
}
|
||||
|
||||
} elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) {
|
||||
// nlog($this->entity_type);
|
||||
} else {
|
||||
|
||||
$query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id);
|
||||
|
||||
}
|
||||
}
|
||||
// $query->exclude(['balance','credit_balance','paid_to_date']);
|
||||
|
||||
// if(auth()->user()->hasIntersectPermissions(['view_client'])){
|
||||
// $query->exclude($this->client_exclusion_fields);
|
||||
// }
|
||||
|
||||
if (request()->has('updated_at') && request()->input('updated_at') > 0) {
|
||||
$query->where('updated_at', '>=', date('Y-m-d H:i:s', intval(request()->input('updated_at'))));
|
||||
|
@ -17,14 +17,10 @@ use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\Admin\EntityFailedSendObject;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceFailedEmailNotification
|
||||
{
|
||||
use UserNotifies, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use UserNotifies;
|
||||
|
||||
public $delay = 7;
|
||||
|
||||
|
@ -11,14 +11,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Excludable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class BankIntegration extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
use Filterable;
|
||||
use Excludable;
|
||||
|
||||
protected $fillable = [
|
||||
'bank_account_name',
|
||||
|
@ -11,15 +11,16 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Models\Traits\Excludable;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Utils\Traits\UserSessionAttributes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class BaseModel
|
||||
@ -33,6 +34,7 @@ class BaseModel extends Model
|
||||
use MakesHash;
|
||||
use UserSessionAttributes;
|
||||
use HasFactory;
|
||||
use Excludable;
|
||||
|
||||
protected $appends = [
|
||||
'hashed_id',
|
||||
|
@ -12,12 +12,14 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\Traits\Excludable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
|
||||
class StaticModel extends Model
|
||||
{
|
||||
use MakesHash;
|
||||
use Excludable;
|
||||
|
||||
protected $casts = [
|
||||
'updated_at' => 'timestamp',
|
||||
|
@ -410,7 +410,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
* @param string $permission '["view_all"]'
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasExactPermission(string $permission = '___'): bool
|
||||
public function hasExactPermissionAndAll(string $permission = '___'): bool
|
||||
{
|
||||
$parts = explode('_', $permission);
|
||||
$all_permission = '__';
|
||||
@ -436,7 +436,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
public function hasIntersectPermissions(array $permissions = []): bool
|
||||
{
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->hasExactPermission($permission)) {
|
||||
if ($this->hasExactPermissionAndAll($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -444,6 +444,22 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when we need to match exactly what permission
|
||||
* the user has, and not aggregate owner and admins.
|
||||
*
|
||||
* This method is used when we need to scope down the query
|
||||
* and display a limited subset.
|
||||
*
|
||||
* @param string $permission '["view_all"]'
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasExactPermission(string $permission = '___'): bool
|
||||
{
|
||||
return (stripos($this->token()->cu->permissions, $permission) !== false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used when we need to match a range of permissions
|
||||
* the user
|
||||
@ -461,13 +477,50 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
}
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->hasExactPermission($permission)) {
|
||||
if ($this->hasExactPermissionAndAll($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when we need to filter permissions carefully.
|
||||
*
|
||||
* For instance, users that have view_client permissions should not
|
||||
* see the client balance, however if they also have
|
||||
* view_invoice or view_all etc, then they should be able to see the balance.
|
||||
*
|
||||
* First we pass over the excluded permissions and return false if we find a match.
|
||||
*
|
||||
* If those permissions are not hit, then we can iterate through the matched_permissions and search for a hit.
|
||||
*
|
||||
* Note, returning FALSE here means the user does NOT have the permission we want to exclude
|
||||
*
|
||||
* @param array $matched_permission
|
||||
* @param array $excluded_permissions
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExcludedPermissions(array $matched_permission = [], array $excluded_permissions = []): bool
|
||||
{
|
||||
if ($this->isSuperUser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($excluded_permissions as $permission) {
|
||||
if ($this->hasExactPermission($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($matched_permission as $permission) {
|
||||
if ($this->hasExactPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function documents()
|
||||
|
@ -75,6 +75,44 @@ class PermissionsTest extends TestCase
|
||||
$company_token->save();
|
||||
}
|
||||
|
||||
public function testHasExcludedPermissions()
|
||||
{
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
$low_cu->permissions = '["view_client"]';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertTrue($this->user->hasExcludedPermissions(["view_client"]));
|
||||
}
|
||||
|
||||
public function testHasExcludedPermissions2()
|
||||
{
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
$low_cu->permissions = '["view_client","edit_all"]';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertFalse($this->user->hasExcludedPermissions(["view_client"], ['edit_all']));
|
||||
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
$low_cu->permissions = 'view_client,edit_all';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertFalse($this->user->hasExcludedPermissions(["view_client"], ['edit_all']));
|
||||
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
$low_cu->permissions = 'view_client,view_all';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertFalse($this->user->hasExcludedPermissions(["view_client"], ['view_all']));
|
||||
|
||||
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
$low_cu->permissions = 'view_client,view_invoice';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertFalse($this->user->hasExcludedPermissions(["view_client"], ['view_invoice']));
|
||||
|
||||
}
|
||||
|
||||
public function testIntersectPermissions()
|
||||
{
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
@ -212,8 +250,8 @@ class PermissionsTest extends TestCase
|
||||
|
||||
public function testExactPermissions()
|
||||
{
|
||||
$this->assertTrue($this->user->hasExactPermission("view_client"));
|
||||
$this->assertFalse($this->user->hasExactPermission("view_all"));
|
||||
$this->assertTrue($this->user->hasExactPermissionAndAll("view_client"));
|
||||
$this->assertFalse($this->user->hasExactPermissionAndAll("view_all"));
|
||||
}
|
||||
|
||||
public function testMissingPermissions()
|
||||
@ -222,8 +260,8 @@ class PermissionsTest extends TestCase
|
||||
$low_cu->permissions = '[""]';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertFalse($this->user->hasExactPermission("view_client"));
|
||||
$this->assertFalse($this->user->hasExactPermission("view_all"));
|
||||
$this->assertFalse($this->user->hasExactPermissionAndAll("view_client"));
|
||||
$this->assertFalse($this->user->hasExactPermissionAndAll("view_all"));
|
||||
}
|
||||
|
||||
public function testViewAllValidPermissions()
|
||||
@ -232,8 +270,8 @@ class PermissionsTest extends TestCase
|
||||
$low_cu->permissions = '["view_all"]';
|
||||
$low_cu->save();
|
||||
|
||||
$this->assertTrue($this->user->hasExactPermission("view_client"));
|
||||
$this->assertTrue($this->user->hasExactPermission("view_all"));
|
||||
$this->assertTrue($this->user->hasExactPermissionAndAll("view_client"));
|
||||
$this->assertTrue($this->user->hasExactPermissionAndAll("view_all"));
|
||||
}
|
||||
|
||||
public function testReturnTypesOfStripos()
|
||||
|
Loading…
x
Reference in New Issue
Block a user