From 79ec6b2ddcd49d316d3c61dbd88262d5779a782b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Jan 2023 10:43:18 +1100 Subject: [PATCH 1/7] Ensure api token has a name using update route --- app/Http/Controllers/BaseController.php | 32 ++++++++++++++++--- .../Requests/Token/UpdateTokenRequest.php | 8 +++++ app/Models/BankIntegration.php | 4 ++- app/Models/User.php | 8 ++--- app/Policies/BankTransactionPolicy.php | 2 +- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 73903e1a8373..bbd2c7b8d04b 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -219,6 +219,12 @@ class BaseController extends Controller return response()->make($error, $httpErrorCode, $headers); } + /** + * Refresh API response with latest cahnges + * @param Builer $query + * @property App\Models\User auth()->user() + * @return Builer + */ protected function refreshResponse($query) { $user = auth()->user(); @@ -443,9 +449,14 @@ class BaseController extends Controller 'company.bank_integrations'=> function ($query) use ($updated_at, $user) { $query->whereNotNull('updated_at'); - if (! $user->isAdmin()) { + if (! $user->hasPermission('view_bank_transaction')) { $query->where('bank_integrations.user_id', $user->id); } + + if(!$user->isAdmin() && !$user->isOwner() && $user->can('create', BankTransaction::class)) { + $query->exclude(["balance"]); + } + }, 'company.bank_transactions'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); @@ -538,9 +549,14 @@ class BaseController extends Controller }, 'company.bank_integrations'=> function ($query) use ($created_at, $user) { - if (! $user->isAdmin()) { + if (! $user->hasPermission('view_bank_transaction')) { $query->where('bank_integrations.user_id', $user->id); } + + if(!$user->isAdmin() && !$user->isOwner() && $user->can('create', BankTransaction::class)) { + $query->exclude(["balance"]); + } + }, 'company.bank_transaction_rules'=> function ($query) use ($user) { @@ -789,9 +805,14 @@ class BaseController extends Controller 'company.bank_integrations'=> function ($query) use ($created_at, $user) { $query->where('created_at', '>=', $created_at); - if (! $user->isAdmin()) { + if (! $user->hasPermission('view_bank_transaction')) { $query->where('bank_integrations.user_id', $user->id); } + + if(!$user->isAdmin() && !$user->isOwner() && $user->can('create', BankTransaction::class)) { + $query->exclude(["balance"]); + } + }, 'company.bank_transactions'=> function ($query) use ($created_at, $user) { $query->where('created_at', '>=', $created_at); @@ -867,7 +888,10 @@ class BaseController extends Controller $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 - $query->where('user_id', '=', auth()->user()->id); + if($this->entity_type == BankIntegration::class && !auth()->user()->isAdmin() && !auth()->user()->isOwner() && auth()->user()->can('create', BankTransaction::class)) + $query->exclude(["balance"]); + else + $query->where('user_id', '=', auth()->user()->id); } elseif(in_array($this->entity_type,[Design::class, GroupSetting::class, PaymentTerm::class])){ // nlog($this->entity_type); diff --git a/app/Http/Requests/Token/UpdateTokenRequest.php b/app/Http/Requests/Token/UpdateTokenRequest.php index 087bd1fb601f..a10a5a592af1 100644 --- a/app/Http/Requests/Token/UpdateTokenRequest.php +++ b/app/Http/Requests/Token/UpdateTokenRequest.php @@ -27,4 +27,12 @@ class UpdateTokenRequest extends Request { return auth()->user()->isAdmin(); } + + public function rules() + { + return [ + 'name' => 'required', + ]; + } + } diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index 042d134ce19b..23d13fe1d841 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -12,13 +12,15 @@ namespace App\Models; use App\Models\Filterable; +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', 'provider_name', diff --git a/app/Models/User.php b/app/Models/User.php index 7ece8fd65535..d10104914f3d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -386,18 +386,18 @@ class User extends Authenticatable implements MustVerifyEmail * @param string $permission '["view_all"]' * @return boolean */ - public function hasExactPermission(string $permission = ''): bool + public function hasExactPermission(string $permission = '___'): bool { $parts = explode('_', $permission); - $all_permission = ''; + $all_permission = '__'; if (count($parts) > 1) { $all_permission = $parts[0].'_all'; } - return (is_int(stripos($this->token()->cu->permissions, $all_permission))) || - (is_int(stripos($this->token()->cu->permissions, $permission))); + return (stripos($this->token()->cu->permissions, $all_permission) !== false) || + (stripos($this->token()->cu->permissions, $permission) !== false); } diff --git a/app/Policies/BankTransactionPolicy.php b/app/Policies/BankTransactionPolicy.php index 9819e0768bc6..b58390df846e 100644 --- a/app/Policies/BankTransactionPolicy.php +++ b/app/Policies/BankTransactionPolicy.php @@ -26,6 +26,6 @@ class BankTransactionPolicy extends EntityPolicy */ public function create(User $user) : bool { - return $user->isAdmin() || $user->hasPermission('create_invoice') || $user->hasPermission('create_all'); + return $user->isAdmin() || $user->hasPermission('create_bank_transaction') || $user->hasPermission('create_all'); } } From a0ac9df5fd87da1d418f6498b58c96db004f65cc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Jan 2023 11:28:23 +1100 Subject: [PATCH 2/7] Improve rate limiting when using send with gmail --- app/Helpers/Mail/GmailTransport.php | 5 ++-- app/Http/Controllers/BaseController.php | 40 +++++-------------------- app/Jobs/Mail/NinjaMailerJob.php | 2 +- app/Models/User.php | 37 +++++++++++++++++++++++ tests/Unit/PermissionsTest.php | 35 ++++++++++++++++++++++ 5 files changed, 84 insertions(+), 35 deletions(-) diff --git a/app/Helpers/Mail/GmailTransport.php b/app/Helpers/Mail/GmailTransport.php index 479fbdbdeb59..a8bf46e0ba83 100644 --- a/app/Helpers/Mail/GmailTransport.php +++ b/app/Helpers/Mail/GmailTransport.php @@ -68,12 +68,13 @@ class GmailTransport extends AbstractTransport try{ $service->users_messages->send('me', $body, []); } - catch(Google\Service\Exception $e) { + catch(\Google\Service\Exception $e) { /* Need to slow down */ if($e->getCode() == '429') { - sleep(5); + sleep(rand(5,10)); + nlog("429 google - retrying "); $service->users_messages->send('me', $body, []); } diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index bbd2c7b8d04b..289b78724add 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -449,11 +449,13 @@ class BaseController extends Controller 'company.bank_integrations'=> function ($query) use ($updated_at, $user) { $query->whereNotNull('updated_at'); + //scopes down permissions for users with no permissions if (! $user->hasPermission('view_bank_transaction')) { $query->where('bank_integrations.user_id', $user->id); } - if(!$user->isAdmin() && !$user->isOwner() && $user->can('create', BankTransaction::class)) { + //allows us to return integrations for users who can create bank transactions + if(!$user->isSuperUser() && $user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) { $query->exclude(["balance"]); } @@ -553,7 +555,7 @@ class BaseController extends Controller $query->where('bank_integrations.user_id', $user->id); } - if(!$user->isAdmin() && !$user->isOwner() && $user->can('create', BankTransaction::class)) { + if(!$user->isSuperUser() && $user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) { $query->exclude(["balance"]); } @@ -809,7 +811,7 @@ class BaseController extends Controller $query->where('bank_integrations.user_id', $user->id); } - if(!$user->isAdmin() && !$user->isOwner() && $user->can('create', BankTransaction::class)) { + if(!$user->isSuperUser() && $user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) { $query->exclude(["balance"]); } @@ -857,39 +859,15 @@ class BaseController extends Controller $query->with($includes); - - - /*Restore here if refactor produces unexpected edge cases*/ -/* - if (auth()->user() && ! auth()->user()->hasPermission('view'.lcfirst(class_basename(Str::snake($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(lcfirst(class_basename(Str::snake($this->entity_type))) == 'user') - $query->where('id', auth()->user()->id); - elseif($this->entity_type == BankTransaction::class){ //table without assigned_user_id - $query->where('user_id', '=', auth()->user()->id); - } - elseif(in_array(lcfirst(class_basename(Str::snake($this->entity_type))),['design','group_setting','payment_term'])){ - //need to pass these back regardless - nlog($this->entity_type); - } - else - $query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id); - - } -*/ - - /*21-01-2023*/ -/**/ - // 10-01-2022 need to ensure we snake case properly here to ensure permissions work as expected - // 28-03-2022 this is definitely correct here, do not append _ to the view, it resolved correctly when snake cased 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()->isAdmin() && !auth()->user()->isOwner() && auth()->user()->can('create', BankTransaction::class)) - $query->exclude(["balance"]); + + 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); } @@ -900,8 +878,6 @@ class BaseController extends Controller $query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id); } -/**/ - // $query->exclude(['balance','credit_balance','paid_to_date']); if (request()->has('updated_at') && request()->input('updated_at') > 0) { diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 5302a41d6397..d3915f2e40e7 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -463,7 +463,7 @@ class NinjaMailerJob implements ShouldQueue $google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); - sleep(rand(2,4)); + sleep(rand(1,6)); } catch(\Exception $e) { $this->logMailError('Gmail Token Invalid', $this->company->clients()->first()); diff --git a/app/Models/User.php b/app/Models/User.php index d10104914f3d..6978dc019520 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -317,6 +317,16 @@ class User extends Authenticatable implements MustVerifyEmail // return $this->company_user->is_owner; } + /** + * Returns true is user is an admin _or_ owner + * + * @return boolean + */ + public function isSuperUser() :bool + { + return $this->token()->cu->is_owner || $this->token()->cu->is_admin; + } + /** * Returns all user created contacts. * @@ -401,6 +411,33 @@ class User extends Authenticatable implements MustVerifyEmail } + /** + * Used when we need to match a range of permissions + * the user + * + * This method is used when we need to scope down the query + * and display a limited subset. + * + * @param array $permissions + * @return boolean + */ + public function hasIntersectPermissions(array $permissions = []): bool + { + + foreach($permissions as $permission) + { + + if($this->hasExactPermission($permission)) + return true; + + } + + return false; + + } + + + public function documents() { return $this->morphMany(Document::class, 'documentable'); diff --git a/tests/Unit/PermissionsTest.php b/tests/Unit/PermissionsTest.php index 484a65dc1d04..0c950cb17f85 100644 --- a/tests/Unit/PermissionsTest.php +++ b/tests/Unit/PermissionsTest.php @@ -79,6 +79,41 @@ class PermissionsTest extends TestCase } + public function testIntersectPermissions() + { + + $low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first(); + $low_cu->permissions = '["view_client"]'; + $low_cu->save(); + + $this->assertFalse($this->user->hasIntersectPermissions(["viewclient"])); + $this->assertTrue($this->user->hasIntersectPermissions(["view_client"])); + + + $low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first(); + $low_cu->permissions = '["view_all"]'; + $low_cu->save(); + + $this->assertFalse($this->user->hasIntersectPermissions(["viewclient"])); + $this->assertTrue($this->user->hasIntersectPermissions(["view_client"])); + + $this->assertFalse($this->user->hasIntersectPermissions(["viewbank_transaction"])); + $this->assertTrue($this->user->hasIntersectPermissions(["view_bank_transaction"])); + + $low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first(); + $low_cu->permissions = '["create_all"]'; + $low_cu->save(); + + $this->assertFalse($this->user->hasIntersectPermissions(["createclient"])); + $this->assertTrue($this->user->hasIntersectPermissions(["create_client"])); + + $this->assertFalse($this->user->hasIntersectPermissions(["createbank_transaction"])); + $this->assertTrue($this->user->hasIntersectPermissions(["create_bank_transaction"])); + $this->assertTrue($this->user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])); + + + } + public function testViewClientPermission() { From a698990c08d9542a0a4639f5ed3f7d79ab389bad Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Jan 2023 11:35:03 +1100 Subject: [PATCH 3/7] minor clean up in filters --- app/Filters/BankIntegrationFilters.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/Filters/BankIntegrationFilters.php b/app/Filters/BankIntegrationFilters.php index 9774af605711..2f5730387b12 100644 --- a/app/Filters/BankIntegrationFilters.php +++ b/app/Filters/BankIntegrationFilters.php @@ -39,14 +39,14 @@ class BankIntegrationFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; } return $this->builder->where(function ($query) use ($filter) { - $query->where('bank_integrations.bank_account_name', 'like', '%'.$filter.'%'); + $query->where('bank_account_name', 'like', '%'.$filter.'%'); }); } @@ -58,7 +58,7 @@ class BankIntegrationFilters extends QueryFilters * @param string filter * @return Builder */ - public function status(string $filter = '') : Builder + public function status(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -90,7 +90,7 @@ class BankIntegrationFilters extends QueryFilters * @param string sort formatted as column|asc * @return Builder */ - public function sort(string $sort) : Builder + public function sort(string $sort): Builder { $sort_col = explode('|', $sort); @@ -102,9 +102,8 @@ class BankIntegrationFilters extends QueryFilters * * @return Illuminate\Database\Query\Builder */ - public function entityFilter() + public function entityFilter(): Builder { - //return $this->builder->whereCompanyId(auth()->user()->company()->id); return $this->builder->company(); } } From 57596ef26ff04a16dd21c84bdd5c0a92735a6628 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Jan 2023 12:58:24 +1100 Subject: [PATCH 4/7] Clean up for Base controller and enhanced permission filers --- app/Http/Controllers/BaseController.php | 94 ++++++++++++++++--------- app/Models/User.php | 27 +++++++ tests/Unit/PermissionsTest.php | 1 - 3 files changed, 86 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 289b78724add..75126c91a6b2 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -15,8 +15,6 @@ use App\Models\Account; use App\Models\BankIntegration; use App\Models\BankTransaction; use App\Models\BankTransactionRule; -use App\Models\ClientGatewayToken; -use App\Models\Company; use App\Models\CompanyGateway; use App\Models\Design; use App\Models\ExpenseCategory; @@ -31,10 +29,8 @@ use App\Transformers\EntityTransformer; use App\Utils\Ninja; use App\Utils\Statics; use App\Utils\Traits\AppSetup; -use App\Utils\TruthSource; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Http\Request; use Illuminate\Support\Str; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; @@ -56,20 +52,20 @@ class BaseController extends Controller * * @var array */ - public $forced_includes; + public $forced_includes = []; /** * Passed from the parent when we need to force * the key of the response object. * @var string */ - public $forced_index; + public $forced_index = 'data'; /** * Fractal manager. * @var object */ - protected $manager; + protected Manager $manager; private $first_load = [ 'account', @@ -146,10 +142,6 @@ class BaseController extends Controller public function __construct() { $this->manager = new Manager(); - - $this->forced_includes = []; - - $this->forced_index = 'data'; } private function buildManager() @@ -165,6 +157,8 @@ class BaseController extends Controller $include = implode(',', $this->forced_includes); } + // $include = $this->filterIncludes($include); + $this->manager->parseIncludes($include); $this->serializer = request()->input('serializer') ?: EntityTransformer::API_SERIALIZER_ARRAY; @@ -187,6 +181,36 @@ class BaseController extends Controller ->header('X-APP-VERSION', config('ninja.app_version')); } + /** + * Filters the includes to ensure the + * end user has the correct permissions to + * view the includes + * + * @param array $includes The includes for the object + * @return string The filtered array of includes + */ + private function filterIncludes(string $includes): string + { + $permissions_array = [ + 'payments' => 'view_payment', + 'client' => 'view_client', + 'clients' => 'view_client', + 'vendor' => 'view_vendor', + 'vendors' => 'view_vendors', + 'expense' => 'view_expense', + 'expenses' => 'view_expense', + ]; + + $collection = collect(explode(",", $includes)); + + $filtered_includes = $collection->filter(function ($include) use ($permissions_array){ + return auth()->user()->hasPermission($permissions_array[$include]); + }); + + return $filtered_includes->implode(","); + + } + /** * 404 for the client portal. * @return Response 404 response @@ -251,7 +275,7 @@ class BaseController extends Controller $query->with( [ - 'company' => function ($query) use ($updated_at, $user) { + 'company' => function ($query) { $query->whereNotNull('updated_at')->with('documents', 'users'); }, 'company.clients' => function ($query) use ($updated_at, $user) { @@ -289,7 +313,7 @@ class BaseController extends Controller $query->where('designs.user_id', $user->id); } }, - 'company.documents'=> function ($query) use ($updated_at, $user) { + 'company.documents'=> function ($query) { $query->where('updated_at', '>=', $updated_at); }, 'company.expenses'=> function ($query) use ($updated_at, $user) { @@ -302,7 +326,7 @@ class BaseController extends Controller }); } }, - 'company.groups' => function ($query) use ($updated_at, $user) { + 'company.groups' => function ($query) { $query->whereNotNull('updated_at')->with('documents'); }, @@ -329,7 +353,7 @@ class BaseController extends Controller } }, - 'company.payment_terms'=> function ($query) use ($updated_at, $user) { + 'company.payment_terms'=> function ($query) use ($user) { $query->whereNotNull('updated_at'); if (! $user->isAdmin()) { @@ -414,7 +438,7 @@ class BaseController extends Controller } }, - 'company.tax_rates'=> function ($query) use ($updated_at, $user) { + 'company.tax_rates'=> function ($query) { $query->whereNotNull('updated_at'); }, 'company.vendors'=> function ($query) use ($updated_at, $user) { @@ -428,10 +452,10 @@ class BaseController extends Controller } }, - 'company.expense_categories'=> function ($query) use ($updated_at, $user) { + 'company.expense_categories'=> function ($query) { $query->whereNotNull('updated_at'); }, - 'company.task_statuses'=> function ($query) use ($updated_at, $user) { + 'company.task_statuses'=> function ($query) { $query->whereNotNull('updated_at'); }, 'company.activities'=> function ($query) use ($user) { @@ -439,14 +463,14 @@ class BaseController extends Controller $query->where('activities.user_id', $user->id); } }, - 'company.subscriptions'=> function ($query) use ($updated_at, $user) { + 'company.subscriptions'=> function ($query) use ($user) { $query->whereNotNull('updated_at'); if (! $user->isAdmin()) { $query->where('subscriptions.user_id', $user->id); } }, - 'company.bank_integrations'=> function ($query) use ($updated_at, $user) { + 'company.bank_integrations'=> function ($query) use ($user) { $query->whereNotNull('updated_at'); //scopes down permissions for users with no permissions @@ -526,22 +550,22 @@ class BaseController extends Controller $query->with( [ - 'company' => function ($query) use ($created_at, $user) { + 'company' => function ($query) { $query->whereNotNull('created_at')->with('documents', 'users'); }, - 'company.designs'=> function ($query) use ($created_at, $user) { + 'company.designs'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at)->with('company'); }, - 'company.documents'=> function ($query) use ($created_at, $user) { + 'company.documents'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at); }, - 'company.groups'=> function ($query) use ($created_at, $user) { + 'company.groups'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at)->with('documents'); }, - 'company.payment_terms'=> function ($query) use ($created_at, $user) { + 'company.payment_terms'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at); }, - 'company.tax_rates'=> function ($query) use ($created_at, $user) { + 'company.tax_rates'=> function ($query) { $query->whereNotNull('created_at'); }, 'company.activities'=> function ($query) use ($user) { @@ -549,7 +573,7 @@ class BaseController extends Controller $query->where('activities.user_id', $user->id); } }, - 'company.bank_integrations'=> function ($query) use ($created_at, $user) { + 'company.bank_integrations'=> function ($query) use ($user) { if (! $user->hasPermission('view_bank_transaction')) { $query->where('bank_integrations.user_id', $user->id); @@ -616,7 +640,7 @@ class BaseController extends Controller $query->with( [ - 'company' => function ($query) use ($created_at, $user) { + 'company' => function ($query) { $query->whereNotNull('created_at')->with('documents', 'users'); }, 'company.clients' => function ($query) use ($created_at, $user) { @@ -647,7 +671,7 @@ class BaseController extends Controller }); } }, - 'company.documents'=> function ($query) use ($created_at, $user) { + 'company.documents'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at); }, 'company.expenses'=> function ($query) use ($created_at, $user) { @@ -660,7 +684,7 @@ class BaseController extends Controller }); } }, - 'company.groups' => function ($query) use ($created_at, $user) { + 'company.groups' => function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at)->with('documents'); }, 'company.invoices'=> function ($query) use ($created_at, $user) { @@ -685,7 +709,7 @@ class BaseController extends Controller } }, - 'company.payment_terms'=> function ($query) use ($created_at, $user) { + 'company.payment_terms'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at); }, 'company.products' => function ($query) use ($created_at, $user) { @@ -752,7 +776,7 @@ class BaseController extends Controller } }, - 'company.tax_rates' => function ($query) use ($created_at, $user) { + 'company.tax_rates' => function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at); }, 'company.vendors'=> function ($query) use ($created_at, $user) { @@ -766,10 +790,10 @@ class BaseController extends Controller } }, - 'company.expense_categories'=> function ($query) use ($created_at, $user) { + 'company.expense_categories'=> function ($query) { $query->whereNotNull('created_at'); }, - 'company.task_statuses'=> function ($query) use ($created_at, $user) { + 'company.task_statuses'=> function ($query) use ($created_at) { $query->where('created_at', '>=', $created_at); }, 'company.activities'=> function ($query) use ($user) { @@ -951,7 +975,7 @@ class BaseController extends Controller return $this->response($this->manager->createData($resource)->toArray()); } - public static function getApiHeaders($count = 0) + public static function getApiHeaders() { return [ 'Content-Type' => 'application/json', diff --git a/app/Models/User.php b/app/Models/User.php index 6978dc019520..542eedfc5ffc 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -436,6 +436,33 @@ class User extends Authenticatable implements MustVerifyEmail } + /** + * Used when we need to match a range of permissions + * the user + * + * This method is used when we need to scope down the query + * and display a limited subset. + * + * @param array $permissions + * @return boolean + */ + public function hasIntersectPermissionsOrAdmin(array $permissions = []): bool + { + + if($this->isSuperUser()) + return true; + + foreach($permissions as $permission) + { + + if($this->hasExactPermission($permission)) + return true; + + } + + return false; + + } public function documents() diff --git a/tests/Unit/PermissionsTest.php b/tests/Unit/PermissionsTest.php index 0c950cb17f85..5226178ec037 100644 --- a/tests/Unit/PermissionsTest.php +++ b/tests/Unit/PermissionsTest.php @@ -111,7 +111,6 @@ class PermissionsTest extends TestCase $this->assertTrue($this->user->hasIntersectPermissions(["create_bank_transaction"])); $this->assertTrue($this->user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])); - } public function testViewClientPermission() From b67bbdcd7ab8f585605fcd6aada8ed081ed18f10 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Jan 2023 09:36:59 +1100 Subject: [PATCH 5/7] Fixes for logic surrounding presenting the save payment method details radio buttons --- .../RecurringInvoice/UpdateRecurringInvoiceRequest.php | 8 ++++---- .../ninja2020/gateways/includes/save_card.blade.php | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index 94fbd0ed7685..0083347a5504 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -112,8 +112,8 @@ class UpdateRecurringInvoiceRequest extends Request $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; } - if (array_key_exists('auto_bill', $input) && isset($input['auto_bill']) && $this->setAutoBillFlag($input['auto_bill'])) { - $input['auto_bill_enabled'] = true; + if (array_key_exists('auto_bill', $input) && isset($input['auto_bill'])) { + $input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']); } if (array_key_exists('documents', $input)) { @@ -132,9 +132,9 @@ class UpdateRecurringInvoiceRequest extends Request * * @return bool */ - private function setAutoBillFlag($auto_bill) :bool + private function setAutoBillFlag($auto_bill) { - if ($auto_bill == 'always') { + if ($auto_bill == 'always' || $auto_bill == 'optout') { return true; } diff --git a/resources/views/portal/ninja2020/gateways/includes/save_card.blade.php b/resources/views/portal/ninja2020/gateways/includes/save_card.blade.php index e319effdcfd4..7a4ea8b41a3f 100644 --- a/resources/views/portal/ninja2020/gateways/includes/save_card.blade.php +++ b/resources/views/portal/ninja2020/gateways/includes/save_card.blade.php @@ -10,8 +10,13 @@ $token_billing = false; $token_billing_string = 'false'; } - - if($gateway_instance->token_billing == 'optout' || $gateway_instance->token_billing == 'always'){ + + if($gateway_instance->token_billing == 'always'){ + $token_billing = false; + $token_billing_string = 'true'; + } + + if($gateway_instance->token_billing == 'optout'){ $checked_on = 'checked'; $checked_off = ''; } From 5cd88484a16c97501082490d513a09d5a83df7cb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Jan 2023 10:04:24 +1100 Subject: [PATCH 6/7] Fixes for task.tax_amount_label --- app/Utils/HtmlEngine.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index e202ab75693e..c8bbe14ab8f0 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -540,6 +540,7 @@ class HtmlEngine $data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$task.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$task.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')]; + $data['$task.tax_amount'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$task.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.gross_line_total')]; $data['$task.service'] = ['value' => '', 'label' => ctrans('texts.service')]; $data['$task.task1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task1')]; From b277f5f3737471c5dc02875f29c0ff5889bb8dcd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Jan 2023 10:17:09 +1100 Subject: [PATCH 7/7] Version update --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 8444e431a72b..cf3ecc59ddfb 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.62 \ No newline at end of file +5.5.63 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index e095532bd1d0..dca28a9ec4d0 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.62', - 'app_tag' => '5.5.62', + 'app_version' => '5.5.63', + 'app_tag' => '5.5.63', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),