Merge pull request #8211 from turbo124/v5-develop

Ensure api token has a name using update route
This commit is contained in:
David Bomba 2023-01-26 10:17:29 +11:00 committed by GitHub
commit 87530190b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 228 additions and 90 deletions

View File

@ -1 +1 @@
5.5.62
5.5.63

View File

@ -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();
}
}

View File

@ -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, []);
}

View File

@ -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
@ -219,6 +243,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();
@ -245,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) {
@ -283,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) {
@ -296,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');
},
@ -323,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()) {
@ -408,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) {
@ -422,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) {
@ -433,19 +463,26 @@ 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');
if (! $user->isAdmin()) {
//scopes down permissions for users with no permissions
if (! $user->hasPermission('view_bank_transaction')) {
$query->where('bank_integrations.user_id', $user->id);
}
//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"]);
}
},
'company.bank_transactions'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
@ -513,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) {
@ -536,11 +573,16 @@ 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->isAdmin()) {
if (! $user->hasPermission('view_bank_transaction')) {
$query->where('bank_integrations.user_id', $user->id);
}
if(!$user->isSuperUser() && $user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) {
$query->exclude(["balance"]);
}
},
'company.bank_transaction_rules'=> function ($query) use ($user) {
@ -598,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) {
@ -629,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) {
@ -642,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) {
@ -667,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) {
@ -734,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) {
@ -748,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) {
@ -789,9 +831,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->isSuperUser() && $user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) {
$query->exclude(["balance"]);
}
},
'company.bank_transactions'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
@ -836,38 +883,17 @@ 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
$query->where('user_id', '=', auth()->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])){
// nlog($this->entity_type);
@ -876,8 +902,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) {
@ -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',

View File

@ -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;
}

View File

@ -27,4 +27,12 @@ class UpdateTokenRequest extends Request
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'name' => 'required',
];
}
}

View File

@ -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());

View File

@ -12,12 +12,14 @@
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',

View File

@ -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.
*
@ -386,21 +396,75 @@ 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);
}
/**
* 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;
}
/**
* 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()
{
return $this->morphMany(Document::class, 'documentable');

View File

@ -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');
}
}

View File

@ -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')];

View File

@ -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', ''),

View File

@ -11,7 +11,12 @@
$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 = '';
}

View File

@ -79,6 +79,40 @@ 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()
{