diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index fd934399fe43..7ffba27472b4 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -15,6 +15,7 @@ use App; use App\DataMapper\ClientSettings; use App\Factory\ClientContactFactory; use App\Factory\VendorContactFactory; +use App\Jobs\Company\CreateCompanyToken; use App\Models\Account; use App\Models\Client; use App\Models\ClientContact; @@ -124,7 +125,8 @@ class CheckData extends Command $this->checkOauthSanity(); $this->checkVendorSettings(); $this->checkClientSettings(); - + $this->checkCompanyTokens(); + if(Ninja::isHosted()){ $this->checkAccountStatuses(); $this->checkNinjaPortalUrls(); @@ -157,6 +159,25 @@ class CheckData extends Command $this->log .= $str."\n"; } + private function checkCompanyTokens() + { + + CompanyUser::doesnthave('token')->cursor()->each(function ($cu){ + + if($cu->user){ + $this->logMessage("Creating missing company token for user # {$cu->user->id} for company id # {$cu->company->id}"); + (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle(); + } + else { + $this->logMessage("Dangling User ID # {$cu->id}"); + } + + }); + + + + } + private function checkOauthSanity() { User::where('oauth_provider_id', '1')->cursor()->each(function ($user){ @@ -422,17 +443,26 @@ class CheckData extends Command $contact_class = VendorContact::class; } - $invitation = new $entity_obj(); - $invitation->company_id = $entity->company_id; - $invitation->user_id = $entity->user_id; - $invitation->{$entity_key} = $entity->id; - $invitation->{$contact_id} = $contact_class::where('company_id', $entity->company_id)->where($client_vendor_key,$entity->{$client_vendor_key})->first()->id; - $invitation->key = Str::random(config('ninja.key_length')); + $invitation = false; - $this->logMessage("Add invitation for {$entity_key} - {$entity->id}"); + //check contact exists! + if($contact_class::where('company_id', $entity->company_id)->where($client_vendor_key,$entity->{$client_vendor_key})->exists()) + { + $invitation = new $entity_obj(); + $invitation->company_id = $entity->company_id; + $invitation->user_id = $entity->user_id; + $invitation->{$entity_key} = $entity->id; + $invitation->{$contact_id} = $contact_class::where('company_id', $entity->company_id)->where($client_vendor_key,$entity->{$client_vendor_key})->first()->id; + $invitation->key = Str::random(config('ninja.key_length')); + $this->logMessage("Add invitation for {$entity_key} - {$entity->id}"); + } + else + $this->logMessage("No contact present, so cannot add invitation for {$entity_key} - {$entity->id}"); try{ - $invitation->save(); + + if($invitation) + $invitation->save(); } catch(\Exception $e){ $this->logMessage($e->getMessage()); diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 3403d0e2128c..76e85b80ce6b 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -12,9 +12,20 @@ namespace App\Http\Controllers; 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; +use App\Models\GroupSetting; +use App\Models\PaymentTerm; +use App\Models\Scheduler; +use App\Models\TaxRate; use App\Models\User; +use App\Models\Webhook; use App\Transformers\ArraySerializer; use App\Transformers\EntityTransformer; use App\Utils\Ninja; @@ -223,6 +234,7 @@ class BaseController extends Controller } $transformer = new $this->entity_transformer($this->serializer); + $updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0; if ($user->getCompany()->is_large && $updated_at == 0) { @@ -240,7 +252,6 @@ class BaseController extends Controller $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents'); if (! $user->hasPermission('view_client')) { - // $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); @@ -259,7 +270,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); if (! $user->hasPermission('view_credit')) { - // $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); @@ -280,7 +290,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('documents'); if (! $user->hasPermission('view_expense')) { - // $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); @@ -290,14 +299,11 @@ class BaseController extends Controller 'company.groups' => function ($query) use ($updated_at, $user) { $query->whereNotNull('updated_at')->with('documents'); - // if(!$user->isAdmin()) - // $query->where('group_settings.user_id', $user->id); }, 'company.invoices'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); if (! $user->hasPermission('view_invoice')) { - // $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { @@ -310,7 +316,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('paymentables', 'documents'); if (! $user->hasPermission('view_payment')) { - // $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); @@ -329,13 +334,11 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('documents'); if (! $user->hasPermission('view_product')) { - // $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); }); - } }, @@ -343,7 +346,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('documents'); if (! $user->hasPermission('view_project')) { - // $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); @@ -355,8 +357,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('documents'); if (! $user->hasPermission('view_purchase_order')) { - // $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); - $query->whereNested(function($query) use ($user) { $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); @@ -368,8 +368,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); if (! $user->hasPermission('view_quote')) { - // $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); - $query->whereNested(function($query) use ($user) { $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); @@ -381,7 +379,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents', 'client.gateway_tokens', 'client.group_settings', 'client.company'); if (! $user->hasPermission('view_recurring_invoice')) { - // $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); @@ -393,7 +390,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('documents'); if (! $user->hasPermission('view_recurring_expense')) { - // $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id); @@ -405,7 +401,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('documents'); if (! $user->hasPermission('view_task')) { - // $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); @@ -420,7 +415,6 @@ class BaseController extends Controller $query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents'); if (! $user->hasPermission('view_vendor')) { - // $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); @@ -611,7 +605,6 @@ class BaseController extends Controller $query->where('clients.created_at', '>=', $created_at)->with('contacts.company', 'gateway_tokens', 'documents'); if (! $user->hasPermission('view_client')) { - // $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); @@ -630,7 +623,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); if (! $user->hasPermission('view_credit')) { - // $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); @@ -644,13 +636,10 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('documents'); if (! $user->hasPermission('view_expense')) { - // $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); }); - - } }, 'company.groups' => function ($query) use ($created_at, $user) { @@ -660,7 +649,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); if (! $user->hasPermission('view_invoice')) { - // $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); @@ -672,7 +660,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('paymentables', 'documents'); if (! $user->hasPermission('view_payment')) { - // $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); @@ -687,7 +674,7 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('documents'); if (! $user->hasPermission('view_product')) { - // $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); + $query->whereNested(function($query) use ($user) { $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); }); @@ -697,7 +684,7 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('documents'); if (! $user->hasPermission('view_project')) { - // $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); + $query->whereNested(function($query) use ($user) { $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); }); @@ -707,7 +694,7 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('documents'); if (! $user->hasPermission('view_purchase_order')) { - // $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); + $query->whereNested(function($query) use ($user) { $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); }); @@ -718,7 +705,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); if (! $user->hasPermission('view_quote')) { - // $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); @@ -730,7 +716,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('invitations', 'documents', 'client.gateway_tokens', 'client.group_settings', 'client.company'); if (! $user->hasPermission('view_recurring_invoice')) { - // $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); @@ -742,7 +727,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('documents'); if (! $user->hasPermission('view_task')) { -// $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); @@ -757,7 +741,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('contacts', 'documents'); if (! $user->hasPermission('view_vendor')) { - // $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); @@ -796,7 +779,6 @@ class BaseController extends Controller $query->where('created_at', '>=', $created_at)->with('documents'); if (! $user->hasPermission('view_recurring_expense')) { - // $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id); $query->whereNested(function($query) use ($user) { $query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id); @@ -854,8 +836,10 @@ class BaseController extends Controller $query->with($includes); - // 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 + + + /*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') @@ -871,6 +855,30 @@ class BaseController extends Controller $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'.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(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); + } + elseif(in_array($this->entity_type,[Design::class, GroupSetting::class, PaymentTerm::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 (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')))); diff --git a/app/Http/Controllers/ClientGatewayTokenController.php b/app/Http/Controllers/ClientGatewayTokenController.php index 44912c5647a4..84b918c09a49 100644 --- a/app/Http/Controllers/ClientGatewayTokenController.php +++ b/app/Http/Controllers/ClientGatewayTokenController.php @@ -18,6 +18,7 @@ use App\Filters\ClientGatewayTokenFilters; use App\Http\Requests\ClientGatewayToken\CreateClientGatewayTokenRequest; use App\Http\Requests\ClientGatewayToken\DestroyClientGatewayTokenRequest; use App\Http\Requests\ClientGatewayToken\EditClientGatewayTokenRequest; +use App\Http\Requests\ClientGatewayToken\ListClientGatewayTokenRequest; use App\Http\Requests\ClientGatewayToken\ShowClientGatewayTokenRequest; use App\Http\Requests\ClientGatewayToken\StoreClientGatewayTokenRequest; use App\Http\Requests\ClientGatewayToken\UpdateClientGatewayTokenRequest; @@ -103,7 +104,7 @@ class ClientGatewayTokenController extends BaseController * @param ClientGatewayTokenFilters $filters * @return Response|mixed */ - public function index(Request $request) + public function index(ListClientGatewayTokenRequest $request) { $client_gateway_token_gateway_tokens = ClientGatewayToken::scope(); diff --git a/app/Http/Requests/ClientGatewayToken/ListClientGatewayTokenRequest.php b/app/Http/Requests/ClientGatewayToken/ListClientGatewayTokenRequest.php new file mode 100644 index 000000000000..afbfe279e95c --- /dev/null +++ b/app/Http/Requests/ClientGatewayToken/ListClientGatewayTokenRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/app/Models/Client.php b/app/Models/Client.php index 7b09fa68f80e..24dd322b942d 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -21,12 +21,12 @@ use App\Models\Project; use App\Models\Quote; use App\Models\Task; use App\Services\Client\ClientService; +use App\Models\Traits\Excludable; use App\Utils\Traits\AppSetup; use App\Utils\Traits\ClientGroupSettingsSaver; use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; -use Exception; use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Cache; @@ -42,6 +42,7 @@ class Client extends BaseModel implements HasLocalePreference use GeneratesCounter; use AppSetup; use ClientGroupSettingsSaver; + use Excludable; protected $presenter = ClientPresenter::class; @@ -132,6 +133,13 @@ class Client extends BaseModel implements HasLocalePreference 'phone', ]; + // public function scopeExclude($query) + // { + // $query->makeHidden(['balance','paid_to_date']); + + // return $query; + // } + public function getEntityType() { return self::class; @@ -417,7 +425,7 @@ class Client extends BaseModel implements HasLocalePreference return $this->company; } - throw new Exception('Could not find a settings object', 1); + throw new \Exception('Could not find a settings object', 1); } public function documents() diff --git a/app/Models/Traits/Excludable.php b/app/Models/Traits/Excludable.php new file mode 100644 index 000000000000..06b494dd2808 --- /dev/null +++ b/app/Models/Traits/Excludable.php @@ -0,0 +1,38 @@ +getConnection()->getSchemaBuilder()->getColumnListing($this->getTable()); + } + + /** + * Exclude an array of elements from the result. + * @param $query + * @param $columns + * + * @return mixed + */ + public function scopeExclude($query, $columns) + { + return $query->select(array_diff($this->getTableColumns(), (array) $columns)); + } + +} + diff --git a/app/Models/User.php b/app/Models/User.php index b27f81f043f7..c8ebf0692297 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -365,6 +365,8 @@ class User extends Authenticatable implements MustVerifyEmail $all_permission = $parts[0].'_all'; } +//empty $all_permissions leads to stripos returning true; + return $this->isOwner() || $this->isAdmin() || (is_int(stripos($this->token()->cu->permissions, $all_permission))) || @@ -372,6 +374,31 @@ class User extends Authenticatable implements MustVerifyEmail } + /** + * 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 + { + + $parts = explode('_', $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))); + + } + public function documents() { return $this->morphMany(Document::class, 'documentable'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 14a1c388c3d8..97174d02c2d8 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -52,7 +52,6 @@ class AppServiceProvider extends ServiceProvider // ); // }); - Relation::morphMap([ 'invoices' => Invoice::class, 'proposals' => Proposal::class, diff --git a/resources/views/email/template/client.blade.php b/resources/views/email/template/client.blade.php index e0c07b128710..2e3a20080956 100644 --- a/resources/views/email/template/client.blade.php +++ b/resources/views/email/template/client.blade.php @@ -1,6 +1,6 @@ @php $primary_color = isset($settings) ? $settings->primary_color : '#4caf50'; - $email_alignment = isset($settings) ? $settings->email_alignment : 'center'; + $email_alignment = isset($settings->email_alignment) ? $settings->email_alignment : 'center'; @endphp diff --git a/tests/Feature/BaseApiTest.php b/tests/Feature/BaseApiTest.php new file mode 100644 index 000000000000..b624387a3963 --- /dev/null +++ b/tests/Feature/BaseApiTest.php @@ -0,0 +1,606 @@ +makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $this->company = $company; + + $company->client_registration_fields = ClientRegistrationFields::generate(); + $settings = CompanySettings::defaults(); + $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; + $settings->website = 'www.invoiceninja.com'; + $settings->address1 = 'Address 1'; + $settings->address2 = 'Address 2'; + $settings->city = 'City'; + $settings->state = 'State'; + $settings->postal_code = 'Postal Code'; + $settings->phone = '555-343-2323'; + $settings->email = 'test@example.com'; + $settings->country_id = '840'; + $settings->vat_number = 'vat number'; + $settings->id_number = 'id number'; + $settings->use_credits_payment = 'always'; + $settings->timezone_id = '1'; + $settings->entity_send_time = 0; + $company->track_inventory = true; + $company->settings = $settings; + $company->save(); + + $this->account->default_company_id = $company->id; + $this->account->save(); + + $owner_user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => $this->createDbHash(config('database.default')), + 'email' => $this->faker->safeEmail(), + ]); + + $this->owner_cu = CompanyUserFactory::create($owner_user->id, $company->id, $this->account->id); + $this->owner_cu->is_owner = true; + $this->owner_cu->is_admin = true; + $this->owner_cu->is_locked = false; + $this->owner_cu->permissions = '[]'; + $this->owner_cu->save(); + + $this->owner_token = \Illuminate\Support\Str::random(64); + + $user_id = $owner_user->id; + + $company_token = new CompanyToken; + $company_token->user_id = $owner_user->id; + $company_token->company_id = $company->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = $this->owner_token; + $company_token->is_system = true; + $company_token->save(); + + + $lower_permission_user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => $this->createDbHash(config('database.default')), + 'email' => $this->faker->safeEmail(), + ]); + + $this->low_cu = CompanyUserFactory::create($lower_permission_user->id, $company->id, $this->account->id); + $this->low_cu->is_owner = false; + $this->low_cu->is_admin = false; + $this->low_cu->is_locked = false; + $this->low_cu->permissions = '["view_task"]'; + $this->low_cu->save(); + + $this->low_token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken; + $company_token->user_id = $lower_permission_user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = $this->low_token; + $company_token->is_system = true; + $company_token->save(); + + Product::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $contact = ClientContact::factory()->create([ + 'user_id' => $user_id, + 'client_id' => $client->id, + 'company_id' => $company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $payment = Payment::factory()->create([ + 'user_id' => $user_id, + 'client_id' => $client->id, + 'company_id' => $company->id, + 'amount' => 10, + ]); + + $contact2 = ClientContact::factory()->create([ + 'user_id' => $user_id, + 'client_id' => $client->id, + 'company_id' => $company->id, + 'send_email' => true, + ]); + + $vendor = Vendor::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'currency_id' => 1, + ]); + + $vendor_contact = VendorContact::factory()->create([ + 'user_id' => $user_id, + 'vendor_id' => $this->vendor->id, + 'company_id' => $company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $vendor_contact2 = VendorContact::factory()->create([ + 'user_id' => $user_id, + 'vendor_id' => $this->vendor->id, + 'company_id' => $company->id, + 'send_email' => true, + ]); + + $project = Project::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'client_id' => $client->id, + ]); + + $expense = Expense::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $recurring_expense = RecurringExpense::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'frequency_id' => 5, + 'remaining_cycles' => 5, + ]); + + $recurring_quote = RecurringQuote::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'client_id' => $client->id, + ]); + + $task = Task::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $invoice = Invoice::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'client_id' => $client->id, + ]); + + $quote = Quote::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'client_id' => $client->id, + ]); + + $credit = Credit::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'client_id' => $client->id, + ]); + + $po = PurchaseOrder::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'vendor_id' => $vendor->id, + ]); + + + $recurring_invoice = RecurringInvoice::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'client_id' => $client->id, + ]); + + $task_status = TaskStatus::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $task->status_id = TaskStatus::where('company_id', $company->id)->first()->id; + $task->save(); + + $expense_category = ExpenseCategory::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $tax_rate = TaxRate::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $gs = new GroupSetting; + $gs->name = 'Test'; + $gs->company_id = $client->company_id; + $gs->settings = ClientSettings::buildClientSettings($company->settings, $client->settings); + + $gs_settings = $gs->settings; + $gs_settings->website = 'http://staging.invoicing.co'; + $gs->settings = $gs_settings; + $gs->save(); + + $scheduler = Scheduler::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $bank_integration = BankIntegration::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'account_id' => $this->account->id, + ]); + + $bank_transaction = BankTransaction::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + 'bank_integration_id' => $bank_integration->id, + ]); + + $bank_transaction_rule = BankTransactionRule::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + + $subscription = Subscription::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $webhook = WebhookFactory::create($company->id, $user_id); + $webhook->save(); + + $document = Document::factory()->create([ + 'user_id' => $user_id, + 'company_id' => $company->id, + ]); + + $cg = new CompanyGateway; + $cg->company_id = $company->id; + $cg->user_id = $user_id; + $cg->gateway_key = 'd14dd26a37cecc30fdd65700bfb55b23'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt('{"publishableKey":"pk_test_P1riKDKD0p","apiKey":"sk_test_Yorqvz45"}'); + $cg->fees_and_limits = []; + $cg->save(); + + $cgt = ClientGatewayTokenFactory::create($company->id); + $cgt->save(); + + } + + // public function testGeneratingClassName() + // { + + // $this->assertEquals('user', Str::snake(User::class)); + + // $this->assertEquals('user',lcfirst(class_basename(Str::snake(User::class)))); + + + // } + + /** + * Tests admin/owner facing routes respond with the correct status and/or data set + */ + public function testOwnerRoutes() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get('/api/v1/users/'); + + $response->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => $json->has('data',2)->etc()); + + /*does not test the number of records however*/ + collect($this->list_routes)->filter(function ($route){ + return !in_array($route, ['users','designs','payment_terms']); + })->each(function($route){ + // nlog($route); + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get("/api/v1/{$route}/") + ->assertJson(fn (AssertableJson $json) => + $json->has('meta') + ->has('data',1) + ); + }); + + } + + public function testOwnerAccessCompany() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->low_token, + ])->get('/api/v1/companies/'.$this->company->hashed_id) + ->assertStatus(401); + + } + + + public function testAdminRoutes() + { + $this->owner_cu = CompanyUser::where('user_id', $this->owner_cu->user_id)->where('company_id', $this->owner_cu->company_id)->first(); + $this->owner_cu->is_owner = false; + $this->owner_cu->is_admin = true; + $this->owner_cu->is_locked = false; + $this->owner_cu->permissions = '[]'; + $this->owner_cu->save(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get('/api/v1/users/'); + + $response->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => $json->has('data',2)->etc()); + + collect($this->list_routes)->filter(function ($route){ + return !in_array($route, ['users','designs','payment_terms']); + })->each(function($route){ + // nlog($route); + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get("/api/v1/{$route}/") + ->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => + $json->has('meta') + ->has('data',1) + ); + }); + + } + + public function testAdminAccessCompany() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get('/api/v1/companies/'.$this->company->hashed_id) + ->assertStatus(200); + + } + + public function testAdminLockedRoutes() + { + $this->owner_cu = CompanyUser::where('user_id', $this->owner_cu->user_id)->where('company_id', $this->owner_cu->company_id)->first(); + $this->owner_cu->is_owner = false; + $this->owner_cu->is_admin = true; + $this->owner_cu->is_locked = true; + $this->owner_cu->permissions = '[]'; + $this->owner_cu->save(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get('/api/v1/users/') + ->assertStatus(403); + + collect($this->list_routes)->filter(function ($route){ + return !in_array($route, ['users','designs','payment_terms']); + })->each(function($route){ + // nlog($route); + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get("/api/v1/{$route}/") + ->assertStatus(403); + }); + + } + + public function testAdminLockedCompany() + { + + $this->owner_cu = CompanyUser::where('user_id', $this->owner_cu->user_id)->where('company_id', $this->owner_cu->company_id)->first(); + $this->owner_cu->is_owner = false; + $this->owner_cu->is_admin = true; + $this->owner_cu->is_locked = true; + $this->owner_cu->permissions = '[]'; + $this->owner_cu->save(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->owner_token, + ])->get('/api/v1/companies/'.$this->company->hashed_id) + ->assertStatus(403); + + } + + + /** + * Tests user facing routes respond with the correct status and/or data set + */ + public function testRestrictedUserRoute() + { + // $permissions = ["view_invoice","view_client","edit_client","edit_invoice","create_invoice","create_client"]; + + // $response = $this->withHeaders([ + // 'X-API-SECRET' => config('ninja.api_secret'), + // 'X-API-TOKEN' => $this->token, + // ])->get('/api/v1/clients/') + // ->assertStatus(200) + // ->assertJson(fn (AssertableJson $json) => $json->has('data',1)->etc()); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/tasks/') + ->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => $json->has('data',1)->etc()); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/group_settings/') + ->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => $json->has('data',2)->etc()); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs/') + ->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => $json->has('data',11)->etc()); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->low_token, + ])->get('/api/v1/users/'); + + $response->assertStatus(200) + ->assertJson(fn (AssertableJson $json) => $json->has('data',1)->etc()); + + collect($this->list_routes)->filter(function ($route){ + return !in_array($route, ['tasks', 'users', 'group_settings','designs','client_gateway_tokens']); + })->each(function($route){ + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->low_token, + ])->get("/api/v1/{$route}/") + ->assertJson(fn (AssertableJson $json) => + $json->has('meta') + ->has('data',0) + ); + + }); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->low_token, + ])->get('/api/v1/companies/'.$this->company->hashed_id) + ->assertStatus(401); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->low_token, + ])->get('/api/v1/client_gateway_tokens/') + ->assertStatus(401); + + } + +} diff --git a/tests/Unit/PermissionsTest.php b/tests/Unit/PermissionsTest.php new file mode 100644 index 000000000000..91fa3e9854a0 --- /dev/null +++ b/tests/Unit/PermissionsTest.php @@ -0,0 +1,125 @@ +faker = \Faker\Factory::create(); + + $account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $account->num_users = 3; + $account->save(); + + $this->company = Company::factory()->create([ + 'account_id' => $account->id, + ]); + + $this->user = User::factory()->create([ + 'account_id' => $account->id, + 'confirmation_code' => '123', + 'email' => $this->faker->safeEmail(), + ]); + + $this->cu = CompanyUserFactory::create($this->user->id, $this->company->id, $account->id); + $this->cu->is_owner = false; + $this->cu->is_admin = false; + $this->cu->is_locked = false; + $this->cu->permissions = '["view_client"]'; + $this->cu->save(); + + $this->token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $account->id; + $company_token->name = 'test token'; + $company_token->token = $this->token; + $company_token->is_system = true; + $company_token->save(); + + } + + public function testExactPermissions() + { + + $this->assertTrue($this->user->hasExactPermission("view_client")); + $this->assertFalse($this->user->hasExactPermission("view_all")); + + } + + public function testMissingPermissions() + { + + $low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first(); + $low_cu->permissions = '[""]'; + $low_cu->save(); + + $this->assertFalse($this->user->hasExactPermission("view_client")); + $this->assertFalse($this->user->hasExactPermission("view_all")); + + } + + public function testViewAllValidPermissions() + { + + $low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first(); + $low_cu->permissions = '["view_all"]'; + $low_cu->save(); + + $this->assertTrue($this->user->hasExactPermission("view_client")); + $this->assertTrue($this->user->hasExactPermission("view_all")); + + } + + public function testViewClientPermission() + { + + $low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first(); + $low_cu->permissions = '["view_client"]'; + $low_cu->save(); + + //this is aberrant + $this->assertTrue($this->user->hasPermission("viewclient")); + + } + +} +