From 872ce3a3bc3e65c6cb5b4baf0dc8808cc6c7ca8a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 18:07:40 +1000 Subject: [PATCH 1/9] Fixes for event failure when viewing an invoice in the portal --- app/Listeners/Invoice/InvoiceViewedActivity.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Listeners/Invoice/InvoiceViewedActivity.php b/app/Listeners/Invoice/InvoiceViewedActivity.php index c3b0e52efecd..b3afaf4032ac 100644 --- a/app/Listeners/Invoice/InvoiceViewedActivity.php +++ b/app/Listeners/Invoice/InvoiceViewedActivity.php @@ -46,10 +46,10 @@ class InvoiceViewedActivity implements ShouldQueue $fields = new \stdClass; - $fields->user_id = $event->invoice->user_id; - $fields->company_id = $event->invoice->company_id; + $fields->user_id = $event->invitation->user_id; + $fields->company_id = $event->invitation->company_id; $fields->activity_type_id = Activity::VIEW_INVOICE; - $fields->client_id = $event->invitation->client_id; + $fields->client_id = $event->invitation->invoice->client_id; $fields->client_contact_id = $event->invitation->client_contact_id; $fields->invitation_id = $event->invitation->id; $fields->invoice_id = $event->invitation->invoice_id; From 46bb38a20cdba99db3c369b57e47711c6c54354b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 18:28:29 +1000 Subject: [PATCH 2/9] Adjust client factories to allow contacts to be created by default --- app/Factory/ClientFactory.php | 4 ++-- app/Repositories/ClientContactRepository.php | 1 + app/Repositories/ClientRepository.php | 2 +- tests/Unit/FactoryCreationTest.php | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Factory/ClientFactory.php b/app/Factory/ClientFactory.php index 9c599ab6e143..7f6460ffb8b2 100644 --- a/app/Factory/ClientFactory.php +++ b/app/Factory/ClientFactory.php @@ -33,8 +33,8 @@ class ClientFactory $client->client_hash = Str::random(40); $client->settings = ClientSettings::defaults(); - $client_contact = ClientContactFactory::create($company_id, $user_id); - $client->contacts->add($client_contact); + // $client_contact = ClientContactFactory::create($company_id, $user_id); + // $client->contacts->add($client_contact); return $client; } diff --git a/app/Repositories/ClientContactRepository.php b/app/Repositories/ClientContactRepository.php index 0ef048db9468..a363f513d48a 100644 --- a/app/Repositories/ClientContactRepository.php +++ b/app/Repositories/ClientContactRepository.php @@ -30,6 +30,7 @@ class ClientContactRepository extends BaseRepository } else { $contacts = collect(); } +info(print_r($client->contacts->toArray(),1)); $client->contacts->pluck('id')->diff($contacts->pluck('id'))->each(function ($contact) { ClientContact::destroy($contact); diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 49d46d692024..8b5582cb71fa 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -78,7 +78,7 @@ class ClientRepository extends BaseRepository $data['name'] = $client->present()->name(); } - info("{$client->present()->name} has a balance of {$client->balance} with a paid to date of {$client->paid_to_date}"); + //info("{$client->present()->name} has a balance of {$client->balance} with a paid to date of {$client->paid_to_date}"); if (array_key_exists('documents', $data)) { $this->saveDocuments($data['documents'], $client); diff --git a/tests/Unit/FactoryCreationTest.php b/tests/Unit/FactoryCreationTest.php index 32eaf760b6fa..47c947da53a9 100644 --- a/tests/Unit/FactoryCreationTest.php +++ b/tests/Unit/FactoryCreationTest.php @@ -126,8 +126,8 @@ class FactoryCreationTest extends TestCase $cliz->save(); $this->assertNotNull($cliz->contacts); - $this->assertEquals(1, $cliz->contacts->count()); - $this->assertInternalType("int", $cliz->contacts->first()->id); + $this->assertEquals(0, $cliz->contacts->count()); + $this->assertInternalType("int", $cliz->id); } /** From 502bd4ad2d4210c6788fc590027948b681db2684 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 18:37:13 +1000 Subject: [PATCH 3/9] Fixes for test/demo data --- app/Console/Commands/CreateTestData.php | 1 + app/Repositories/ClientContactRepository.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index ac7bcf8daa56..0045796945a9 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -286,6 +286,7 @@ class CreateTestData extends Command $company = factory(\App\Models\Company::class)->create([ 'account_id' => $account->id, 'slack_webhook_url' => config('ninja.notification.slack'), + 'is_large' => true, ]); $account->default_company_id = $company->id; diff --git a/app/Repositories/ClientContactRepository.php b/app/Repositories/ClientContactRepository.php index a363f513d48a..0ef048db9468 100644 --- a/app/Repositories/ClientContactRepository.php +++ b/app/Repositories/ClientContactRepository.php @@ -30,7 +30,6 @@ class ClientContactRepository extends BaseRepository } else { $contacts = collect(); } -info(print_r($client->contacts->toArray(),1)); $client->contacts->pluck('id')->diff($contacts->pluck('id'))->each(function ($contact) { ClientContact::destroy($contact); From f28a604d84cd02f9918352f23ad94c2122781b0a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 19:49:09 +1000 Subject: [PATCH 4/9] Fixes for invoicewasviewed --- app/Http/Controllers/ClientPortal/InvitationController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index 8453e788f76c..533c419e067a 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -54,7 +54,7 @@ class InvitationController extends Controller event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars())); - $this->fireEntityViewedEvent($invitation->{$entity}, $entity); + $this->fireEntityViewedEvent($invitation, $entity); } return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})]); From d5b777206e884fd7fcd88e8966daf7167c971e85 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 21:19:51 +1000 Subject: [PATCH 5/9] Shop routes --- .../Controllers/Shop/InvoiceController.php | 83 +++++++++++++++++++ .../Controllers/Shop/ProductController.php | 53 ++++++++++++ app/Http/Kernel.php | 7 ++ app/Http/Middleware/Shop/ShopTokenAuth.php | 78 +++++++++++++++++ app/Http/Middleware/TokenAuth.php | 14 ++++ app/Models/CompanyUser.php | 1 + app/Providers/RouteServiceProvider.php | 10 +++ .../2020_07_28_104218_shop_token.php | 30 +++++++ routes/shop.php | 14 ++++ 9 files changed, 290 insertions(+) create mode 100644 app/Http/Controllers/Shop/InvoiceController.php create mode 100644 app/Http/Controllers/Shop/ProductController.php create mode 100644 app/Http/Middleware/Shop/ShopTokenAuth.php create mode 100644 database/migrations/2020_07_28_104218_shop_token.php create mode 100644 routes/shop.php diff --git a/app/Http/Controllers/Shop/InvoiceController.php b/app/Http/Controllers/Shop/InvoiceController.php new file mode 100644 index 000000000000..b0723ae6fb1a --- /dev/null +++ b/app/Http/Controllers/Shop/InvoiceController.php @@ -0,0 +1,83 @@ +invoice_repo = $invoice_repo; + } + + public function show(string $invitation_key) + { + $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + + $invitation = InvoiceInvitation::with(['invoice']) + ->where('company_id', $company_token->company->id) + ->where('key',$invitation_key) + ->firstOrFail(); + + return $this->itemResponse($invitation->invoice); + } + + /** + * Display a listing of the resource. + * + * @return \Illuminate\Http\Response + */ + public function store(StoreInvoiceRequest $request) + { + $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + + $client = Client::find($request->input('client_id')); + + $invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create($company_token->company_id, $company_token->user_id)); + + event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars())); + + $invoice = $invoice->service()->triggeredActions($request)->save(); + + return $this->itemResponse($invoice); + } + +} diff --git a/app/Http/Controllers/Shop/ProductController.php b/app/Http/Controllers/Shop/ProductController.php new file mode 100644 index 000000000000..c68131d51451 --- /dev/null +++ b/app/Http/Controllers/Shop/ProductController.php @@ -0,0 +1,53 @@ +whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + + $products = Product::where('company_id', $company_token->company->id); + + return $this->listResponse($products); + } + + public function show(string $product_key) + { + $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + + $product = Product::where('company_id', $company_token->company->id) + ->where('product_key', $product_key) + ->first(); + + return $this->itemResponse($product); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4799bf0fc7ab..9ec5e5972e5e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -73,6 +73,11 @@ class Kernel extends HttpKernel \App\Http\Middleware\StartupCheck::class, \App\Http\Middleware\QueryLogging::class, ], + 'shop' => [ + 'throttle:60,1', + 'bindings', + 'query_logging', + ], ]; /** @@ -108,5 +113,7 @@ class Kernel extends HttpKernel 'api_db' => \App\Http\Middleware\SetDb::class, 'locale' => \App\Http\Middleware\Locale::class, 'contact.register' => \App\Http\Middleware\ContactRegister::class, + 'shop_token_auth' => \App\Http\Middleware\ShopTokenAuth::class, + ]; } diff --git a/app/Http/Middleware/Shop/ShopTokenAuth.php b/app/Http/Middleware/Shop/ShopTokenAuth.php new file mode 100644 index 000000000000..2af0eb0df4de --- /dev/null +++ b/app/Http/Middleware/Shop/ShopTokenAuth.php @@ -0,0 +1,78 @@ +header('X-API-TOKEN') && ($company_token = CompanyToken::with(['user','company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first())) { + + /* Check if this is a restricted token*/ + if(!$company_token->shop_restricted){ + + $error = [ + 'message' => 'Cannot use a unrestricted token on this route', + 'errors' => [] + ]; + + + return response()->json($error, 403); + + } + + $user = $company_token->user; + + $error = [ + 'message' => 'User inactive', + 'errors' => [] + ]; + + //user who once existed, but has been soft deleted + if (!$user) { + return response()->json($error, 403); + } + + /* + | + | Necessary evil here: As we are authenticating on CompanyToken, + | we need to link the company to the user manually. This allows + | us to decouple a $user and their attached companies completely. + | + */ + $user->setCompany($company_token->company); + + config(['ninja.company_id' => $company_token->company->id]); + + app('queue')->createPayloadUsing(function () use ($company_token) { + return ['db' => $company_token->company->db]; + }); + + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/TokenAuth.php b/app/Http/Middleware/TokenAuth.php index b3f840fa6ece..9b4ebda3e4fd 100644 --- a/app/Http/Middleware/TokenAuth.php +++ b/app/Http/Middleware/TokenAuth.php @@ -29,6 +29,20 @@ class TokenAuth public function handle($request, Closure $next) { if ($request->header('X-API-TOKEN') && ($company_token = CompanyToken::with(['user','company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first())) { + + if($company_token->shop_restricted){ + + $error = [ + 'message' => 'Cannot use a restricted token on this route', + 'errors' => [] + ]; + + + return response()->json($error, 403); + + } + + $user = $company_token->user; $error = [ diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index 5ce07b3efdc7..fa699b40e274 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -46,6 +46,7 @@ class CompanyUser extends Pivot 'is_owner', 'is_locked', 'slack_webhook_url', + 'shop_restricted' ]; protected $touches = []; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 778e1d5ff03a..feddf0b08bc2 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -57,6 +57,8 @@ class RouteServiceProvider extends ServiceProvider $this->mapContactApiRoutes(); $this->mapClientApiRoutes(); + + $this->mapShopApiRoutes(); } /** @@ -117,4 +119,12 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace) ->group(base_path('routes/client.php')); } + + protected function mapShopApiRoutes() + { + Route::prefix('') + ->middleware('shop') + ->namespace($this->namespace) + ->group(base_path('routes/shop.php')); + } } diff --git a/database/migrations/2020_07_28_104218_shop_token.php b/database/migrations/2020_07_28_104218_shop_token.php new file mode 100644 index 000000000000..49dadf527b80 --- /dev/null +++ b/database/migrations/2020_07_28_104218_shop_token.php @@ -0,0 +1,30 @@ +boolean('shop_restricted')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/routes/shop.php b/routes/shop.php new file mode 100644 index 000000000000..783a503a46de --- /dev/null +++ b/routes/shop.php @@ -0,0 +1,14 @@ + ['api_db','shop_token_auth','locale']], function () { + + Route::get('products', 'Shop\ProductController@index'); + Route::get('clients', 'Shop\ClientController@index'); + Route::get('invoices', 'Shop\InvoiceController@index'); + Route::get('client/{contact_key}', 'Shop\ClientController@show'); + Route::get('invoice/{invitation_key}', 'Shop\InvoiceController@show'); + Route::get('product/{product_key}', 'Shop\ProductController@show'); + +}); \ No newline at end of file From a8a0c7695c5c6a5dde15e2e229308924e98be6cc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 21:30:11 +1000 Subject: [PATCH 6/9] Shop routes --- .../Controllers/Shop/ClientController.php | 77 +++++++++++++++++++ .../Controllers/Shop/InvoiceController.php | 8 +- routes/api.php | 4 +- 3 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 app/Http/Controllers/Shop/ClientController.php diff --git a/app/Http/Controllers/Shop/ClientController.php b/app/Http/Controllers/Shop/ClientController.php new file mode 100644 index 000000000000..2fec7c5d2fa0 --- /dev/null +++ b/app/Http/Controllers/Shop/ClientController.php @@ -0,0 +1,77 @@ +client_repo = $client_repo; + } + + public function show(string $contact_key) + { + $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + + $contact = ClientContact::with('client') + ->where('company_id', $company_token->company->id) + ->where('contact_key', $contact_key) + ->firstOrFail(); + + return $this->itemResponse($contact->client); + } + + public function store(StoreClientRequest $request) + { + $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + + $client = $this->client_repo->save($request->all(), ClientFactory::create($company_token->company_id, $company_token->user_id)); + + $client->load('contacts', 'primary_contact'); + + $this->uploadLogo($request->file('company_logo'), $client->company, $client); + + event(new ClientWasCreated($client, $client->company, Ninja::eventVars())); + + return $this->itemResponse($client); + } +} diff --git a/app/Http/Controllers/Shop/InvoiceController.php b/app/Http/Controllers/Shop/InvoiceController.php index b0723ae6fb1a..97b5a3e25e51 100644 --- a/app/Http/Controllers/Shop/InvoiceController.php +++ b/app/Http/Controllers/Shop/InvoiceController.php @@ -11,6 +11,7 @@ namespace App\Http\Controllers\Shop; +use App\Events\Invoice\InvoiceWasCreated; use App\Factory\InvoiceFactory; use App\Http\Controllers\BaseController; use App\Http\Requests\Invoice\StoreInvoiceRequest; @@ -20,6 +21,7 @@ use App\Models\Invoice; use App\Models\InvoiceInvitation; use App\Repositories\InvoiceRepository; use App\Transformers\InvoiceTransformer; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; @@ -60,11 +62,7 @@ class InvoiceController extends BaseController return $this->itemResponse($invitation->invoice); } - /** - * Display a listing of the resource. - * - * @return \Illuminate\Http\Response - */ + public function store(StoreInvoiceRequest $request) { $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); diff --git a/routes/api.php b/routes/api.php index e1feb2bdeea4..1db2f96a6b8d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -136,8 +136,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::post('emails', 'EmailController@send')->name('email.send'); /*Subscription and Webhook routes */ - Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe'); - Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe'); + // Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe'); + // Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe'); Route::resource('webhooks', 'WebhookController'); Route::post('webhooks/bulk', 'WebhookController@bulk')->name('webhooks.bulk'); From caad3661d52c1337d9a65a6c259e92f2b1f41f6b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 21:58:15 +1000 Subject: [PATCH 7/9] Refactor to user company_key instead of company_token --- app/Http/Controllers/BaseController.php | 2 +- .../Controllers/Shop/ClientController.php | 16 ++-- .../Controllers/Shop/InvoiceController.php | 14 ++-- .../Controllers/Shop/ProductController.php | 13 ++-- app/Http/Kernel.php | 3 +- app/Http/Middleware/SetDbByCompanyKey.php | 48 ++++++++++++ app/Http/Middleware/Shop/ShopTokenAuth.php | 78 ------------------- app/Http/Middleware/TokenAuth.php | 13 ---- app/Libraries/MultiDB.php | 11 +++ .../2020_07_28_104218_shop_token.php | 4 +- routes/shop.php | 14 ++-- 11 files changed, 97 insertions(+), 119 deletions(-) create mode 100644 app/Http/Middleware/SetDbByCompanyKey.php delete mode 100644 app/Http/Middleware/Shop/ShopTokenAuth.php diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index e741f1466da2..5033780abbc6 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -270,7 +270,7 @@ class BaseController extends Controller $query->with($includes); - if (!auth()->user()->hasPermission('view_'.lcfirst(class_basename($this->entity_type)))) { + if (auth()->user() && !auth()->user()->hasPermission('view_'.lcfirst(class_basename($this->entity_type)))) { $query->where('user_id', '=', auth()->user()->id); } diff --git a/app/Http/Controllers/Shop/ClientController.php b/app/Http/Controllers/Shop/ClientController.php index 2fec7c5d2fa0..7219e857e77f 100644 --- a/app/Http/Controllers/Shop/ClientController.php +++ b/app/Http/Controllers/Shop/ClientController.php @@ -50,10 +50,10 @@ class ClientController extends BaseController public function show(string $contact_key) { - $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); $contact = ClientContact::with('client') - ->where('company_id', $company_token->company->id) + ->where('company_id', $company->id) ->where('contact_key', $contact_key) ->firstOrFail(); @@ -62,15 +62,19 @@ class ClientController extends BaseController public function store(StoreClientRequest $request) { - $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); - $client = $this->client_repo->save($request->all(), ClientFactory::create($company_token->company_id, $company_token->user_id)); + app('queue')->createPayloadUsing(function () use ($company) { + return ['db' => $company->db]; + }); + + $client = $this->client_repo->save($request->all(), ClientFactory::create($company->id, $company->owner()->id)); $client->load('contacts', 'primary_contact'); - $this->uploadLogo($request->file('company_logo'), $client->company, $client); + $this->uploadLogo($request->file('company_logo'), $company, $client); - event(new ClientWasCreated($client, $client->company, Ninja::eventVars())); + event(new ClientWasCreated($client, $company, Ninja::eventVars())); return $this->itemResponse($client); } diff --git a/app/Http/Controllers/Shop/InvoiceController.php b/app/Http/Controllers/Shop/InvoiceController.php index 97b5a3e25e51..198613dc516e 100644 --- a/app/Http/Controllers/Shop/InvoiceController.php +++ b/app/Http/Controllers/Shop/InvoiceController.php @@ -52,10 +52,10 @@ class InvoiceController extends BaseController public function show(string $invitation_key) { - $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); $invitation = InvoiceInvitation::with(['invoice']) - ->where('company_id', $company_token->company->id) + ->where('company_id', $company->id) ->where('key',$invitation_key) ->firstOrFail(); @@ -65,13 +65,17 @@ class InvoiceController extends BaseController public function store(StoreInvoiceRequest $request) { - $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + app('queue')->createPayloadUsing(function () use ($company) { + return ['db' => $company->db]; + }); + + $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); $client = Client::find($request->input('client_id')); - $invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create($company_token->company_id, $company_token->user_id)); + $invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create($company_id, $company->owner()->id)); - event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars())); + event(new InvoiceWasCreated($invoice, $company, Ninja::eventVars())); $invoice = $invoice->service()->triggeredActions($request)->save(); diff --git a/app/Http/Controllers/Shop/ProductController.php b/app/Http/Controllers/Shop/ProductController.php index c68131d51451..5122ba1918c4 100644 --- a/app/Http/Controllers/Shop/ProductController.php +++ b/app/Http/Controllers/Shop/ProductController.php @@ -12,6 +12,7 @@ namespace App\Http\Controllers\Shop; use App\Http\Controllers\BaseController; +use App\Models\Company; use App\Models\CompanyToken; use App\Models\Product; use App\Transformers\ProductTransformer; @@ -31,20 +32,20 @@ class ProductController extends BaseController * * @return \Illuminate\Http\Response */ - public function index() + public function index(Request $request) { - $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); - $products = Product::where('company_id', $company_token->company->id); + $products = Product::where('company_id', $company->id); return $this->listResponse($products); } - public function show(string $product_key) + public function show(Request $request, string $product_key) { - $company_token = CompanyToken::with(['company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first(); + $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); - $product = Product::where('company_id', $company_token->company->id) + $product = Product::where('company_id', $company->id) ->where('product_key', $product_key) ->first(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 9ec5e5972e5e..ca3680122a97 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -111,9 +111,10 @@ class Kernel extends HttpKernel 'url_db' => \App\Http\Middleware\UrlSetDb::class, 'web_db' => \App\Http\Middleware\SetWebDb::class, 'api_db' => \App\Http\Middleware\SetDb::class, + 'company_key_db' => \App\Http\Middleware\SetDbByCompanyKey::class, 'locale' => \App\Http\Middleware\Locale::class, 'contact.register' => \App\Http\Middleware\ContactRegister::class, - 'shop_token_auth' => \App\Http\Middleware\ShopTokenAuth::class, + 'shop_token_auth' => \App\Http\Middleware\Shop\ShopTokenAuth::class, ]; } diff --git a/app/Http/Middleware/SetDbByCompanyKey.php b/app/Http/Middleware/SetDbByCompanyKey.php new file mode 100644 index 000000000000..74c995127244 --- /dev/null +++ b/app/Http/Middleware/SetDbByCompanyKey.php @@ -0,0 +1,48 @@ + 'Invalid Token', + 'errors' => [] + ]; + + + if ($request->header('X-API-COMPANY_KEY') && config('ninja.db.multi_db_enabled')) { + if (! MultiDB::findAndSetDbByCompanyKey($request->header('X-API-COMPANY_KEY'))) { + return response()->json($error, 403); + } + } elseif (!config('ninja.db.multi_db_enabled')) { + return $next($request); + } else { + return response()->json($error, 403); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Shop/ShopTokenAuth.php b/app/Http/Middleware/Shop/ShopTokenAuth.php deleted file mode 100644 index 2af0eb0df4de..000000000000 --- a/app/Http/Middleware/Shop/ShopTokenAuth.php +++ /dev/null @@ -1,78 +0,0 @@ -header('X-API-TOKEN') && ($company_token = CompanyToken::with(['user','company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first())) { - - /* Check if this is a restricted token*/ - if(!$company_token->shop_restricted){ - - $error = [ - 'message' => 'Cannot use a unrestricted token on this route', - 'errors' => [] - ]; - - - return response()->json($error, 403); - - } - - $user = $company_token->user; - - $error = [ - 'message' => 'User inactive', - 'errors' => [] - ]; - - //user who once existed, but has been soft deleted - if (!$user) { - return response()->json($error, 403); - } - - /* - | - | Necessary evil here: As we are authenticating on CompanyToken, - | we need to link the company to the user manually. This allows - | us to decouple a $user and their attached companies completely. - | - */ - $user->setCompany($company_token->company); - - config(['ninja.company_id' => $company_token->company->id]); - - app('queue')->createPayloadUsing(function () use ($company_token) { - return ['db' => $company_token->company->db]; - }); - - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/TokenAuth.php b/app/Http/Middleware/TokenAuth.php index 9b4ebda3e4fd..088e51cd43f2 100644 --- a/app/Http/Middleware/TokenAuth.php +++ b/app/Http/Middleware/TokenAuth.php @@ -30,19 +30,6 @@ class TokenAuth { if ($request->header('X-API-TOKEN') && ($company_token = CompanyToken::with(['user','company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first())) { - if($company_token->shop_restricted){ - - $error = [ - 'message' => 'Cannot use a restricted token on this route', - 'errors' => [] - ]; - - - return response()->json($error, 403); - - } - - $user = $company_token->user; $error = [ diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 457eee507023..d15196a40bce 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -180,6 +180,17 @@ class MultiDB return false; } + public static function findAndSetDbByCompanyKey($company_key) :bool + { + foreach (self::$dbs as $db) { + if ($company = Company::on($db)->where('company_key', $company_key)->first()) { + self::setDb($company->db); + return true; + } + } + return false; + } + public static function findAndSetDbByDomain($subdomain) :bool { foreach (self::$dbs as $db) { diff --git a/database/migrations/2020_07_28_104218_shop_token.php b/database/migrations/2020_07_28_104218_shop_token.php index 49dadf527b80..7996ffa0a8b9 100644 --- a/database/migrations/2020_07_28_104218_shop_token.php +++ b/database/migrations/2020_07_28_104218_shop_token.php @@ -13,8 +13,8 @@ class ShopToken extends Migration */ public function up() { - Schema::table('company_user', function (Blueprint $table) { - $table->boolean('shop_restricted')->default(false); + Schema::table('companies', function (Blueprint $table) { + $table->boolean('enable_shop_api')->default(false); }); } diff --git a/routes/shop.php b/routes/shop.php index 783a503a46de..aee2d2f75669 100644 --- a/routes/shop.php +++ b/routes/shop.php @@ -2,13 +2,13 @@ use Illuminate\Support\Facades\Route; -Route::group(['middleware' => ['api_db','shop_token_auth','locale']], function () { +Route::group(['middleware' => ['company_key_db','locale'], 'prefix' => 'api/v1'], function () { - Route::get('products', 'Shop\ProductController@index'); - Route::get('clients', 'Shop\ClientController@index'); - Route::get('invoices', 'Shop\InvoiceController@index'); - Route::get('client/{contact_key}', 'Shop\ClientController@show'); - Route::get('invoice/{invitation_key}', 'Shop\InvoiceController@show'); - Route::get('product/{product_key}', 'Shop\ProductController@show'); + Route::get('shop/products', 'Shop\ProductController@index'); + Route::get('shop/clients', 'Shop\ClientController@index'); + Route::get('shop/invoices', 'Shop\InvoiceController@index'); + Route::get('shop/client/{contact_key}', 'Shop\ClientController@show'); + Route::get('shop/invoice/{invitation_key}', 'Shop\InvoiceController@show'); + Route::get('shop/product/{product_key}', 'Shop\ProductController@show'); }); \ No newline at end of file From de78ea1506451693240f069b61a727111d39c06b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 22:05:17 +1000 Subject: [PATCH 8/9] Shop --- .../Controllers/Shop/ClientController.php | 3 +- tests/Feature/Shop/ShopInvoiceTest.php | 101 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/Shop/ShopInvoiceTest.php diff --git a/app/Http/Controllers/Shop/ClientController.php b/app/Http/Controllers/Shop/ClientController.php index 7219e857e77f..31002e57b4fd 100644 --- a/app/Http/Controllers/Shop/ClientController.php +++ b/app/Http/Controllers/Shop/ClientController.php @@ -17,6 +17,7 @@ use App\Http\Controllers\BaseController; use App\Http\Requests\Client\StoreClientRequest; use App\Models\Client; use App\Models\ClientContact; +use App\Models\Company; use App\Models\CompanyToken; use App\Repositories\ClientRepository; use App\Transformers\ClientTransformer; @@ -48,7 +49,7 @@ class ClientController extends BaseController $this->client_repo = $client_repo; } - public function show(string $contact_key) + public function show(Request $request, string $contact_key) { $company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first(); diff --git a/tests/Feature/Shop/ShopInvoiceTest.php b/tests/Feature/Shop/ShopInvoiceTest.php new file mode 100644 index 000000000000..26657dc6be0d --- /dev/null +++ b/tests/Feature/Shop/ShopInvoiceTest.php @@ -0,0 +1,101 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + } + + public function testTokenFailure() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-COMPANY_KEY' => $this->company->company_key + ])->get('/api/v1/shop/products'); + + + $response->assertStatus(200); + } + + public function testTokenSuccess() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-COMPANY_KEY' => $this->company->company_key + ])->get('/api/v1/products'); + + + $response->assertStatus(403); + + $arr = $response->json(); + } + + public function testGetByProductKey() + { + $product = factory(\App\Models\Product::class)->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + ]); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-COMPANY_KEY' => $this->company->company_key + ])->get('/api/v1/shop/product/'.$product->product_key); + + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals($product->hashed_id, $arr['data']['id']); + } + + public function testGetByClientByContactKey() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-COMPANY_KEY' => $this->company->company_key + ])->get('/api/v1/shop/client/'.$this->client->contacts->first()->contact_key); + + + $response->assertStatus(200); + $arr = $response->json(); + + $this->assertEquals($this->client->hashed_id, $arr['data']['id']); + + } +} From 2b9610ea022eb51d171de5d3fbfc4f17701d076e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 28 Jul 2020 22:06:47 +1000 Subject: [PATCH 9/9] clean up for basecontroller --- app/Http/Controllers/BaseController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 5033780abbc6..ccf9ee51c46b 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -346,7 +346,7 @@ class BaseController extends Controller $data = $this->createItem($item, $transformer, $this->entity_type); - if (request()->include_static) { + if (auth()->user() && request()->include_static) { $data['static'] = Statics::company(auth()->user()->getCompany()->getLocale()); }