diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 369c1d08c580..bb87f5181f15 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -619,7 +619,7 @@ class BankIntegrationController extends BaseController if(!$bank_account_id) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $bi = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->where('company_id', auth()->user()->company()->id)->firstOrFail(); + $bi = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->company()->firstOrFail(); $yodlee = new Yodlee($bank_account_id); $res = $yodlee->deleteAccount($acc_id); diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 72a82fbf4b08..9930d47ddd24 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -498,17 +498,19 @@ class ClientController extends BaseController public function bulk(BulkClientRequest $request) { - $ids = request()->input('ids'); - $clients = Client::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor(); $action = $request->action; - $clients->each(function ($client, $key) use ($action) { - if (auth()->user()->can('edit', $client)) { - $this->client_repo->{$action}($client); - } - }); + $clients = Client::withTrashed() + ->company() + ->whereIn('id', $request->ids) + ->cursor() + ->each(function ($client) use ($action) { + if (auth()->user()->can('edit', $client)) { + $this->client_repo->{$action}($client); + } + }); - return $this->listResponse(Client::withTrashed()->whereIn('id', $this->transformKeys($ids))); + return $this->listResponse(Client::withTrashed()->company()->whereIn('id', $request->ids)); } /** diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index a0239261fb22..a1781a0dac44 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -505,11 +505,12 @@ class CompanyGatewayController extends BaseController $company_gateways = CompanyGateway::withTrashed() ->whereIn('id',$this->transformKeys($ids)) + ->company() ->cursor() ->each(function ($company_gateway, $key) use ($action) { $this->company_repo->{$action}($company_gateway); }); - return $this->listResponse(CompanyGateway::withTrashed()->whereIn('id', $this->transformKeys($ids))); + return $this->listResponse(CompanyGateway::withTrashed()->company()->whereIn('id', $this->transformKeys($ids))); } } diff --git a/app/Http/Controllers/CompanyLedgerController.php b/app/Http/Controllers/CompanyLedgerController.php index 37bda0a5f290..ca95706c745b 100644 --- a/app/Http/Controllers/CompanyLedgerController.php +++ b/app/Http/Controllers/CompanyLedgerController.php @@ -64,7 +64,7 @@ class CompanyLedgerController extends BaseController */ public function index(ShowCompanyLedgerRequest $request) { - $company_ledger = CompanyLedger::whereCompanyId(auth()->user()->company()->id)->orderBy('id', 'ASC'); + $company_ledger = CompanyLedger::where('company_id', auth()->user()->company()->id)->orderBy('id', 'ASC'); return $this->listResponse($company_ledger); } diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 56ab93d5710d..7aa784594503 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -498,7 +498,6 @@ class CreditController extends BaseController public function bulk(BulkCreditRequest $request) { $action = $request->input('action'); - $ids = $request->input('ids'); if(Ninja::isHosted() && (stripos($action, 'email') !== false) && !auth()->user()->company()->account->account_sms_verified) @@ -548,7 +547,7 @@ class CreditController extends BaseController } }); - return $this->listResponse(Credit::withTrashed()->whereIn('id', $this->transformKeys($ids))); + return $this->listResponse(Credit::withTrashed()->company()->whereIn('id', $this->transformKeys($ids))); } public function action(ActionCreditRequest $request, Credit $credit, $action) diff --git a/app/Http/Controllers/DesignController.php b/app/Http/Controllers/DesignController.php index 112a646b2b5e..e127639e216d 100644 --- a/app/Http/Controllers/DesignController.php +++ b/app/Http/Controllers/DesignController.php @@ -520,7 +520,7 @@ class DesignController extends BaseController $ids = request()->input('ids'); - $designs = Design::withTrashed()->find($this->transformKeys($ids)); + $designs = Design::withTrashed()->company()->whereIn('id', $this->transformKeys($ids)); $designs->each(function ($design, $key) use ($action) { if (auth()->user()->can('edit', $design)) { @@ -528,7 +528,7 @@ class DesignController extends BaseController } }); - return $this->listResponse(Design::withTrashed()->whereIn('id', $this->transformKeys($ids))); + return $this->listResponse(Design::withTrashed()->company()->whereIn('id', $this->transformKeys($ids))); } public function default(DefaultDesignRequest $request) diff --git a/app/Http/Requests/Client/BulkClientRequest.php b/app/Http/Requests/Client/BulkClientRequest.php index ecc11e2cbe50..dab00c23a5a8 100644 --- a/app/Http/Requests/Client/BulkClientRequest.php +++ b/app/Http/Requests/Client/BulkClientRequest.php @@ -12,9 +12,14 @@ namespace App\Http\Requests\Client; use App\Http\Requests\Request; +use App\Models\Client; +use App\Utils\Traits\MakesHash; +use Illuminate\Validation\Rule; class BulkClientRequest extends Request { + use MakesHash; + /** * Determine if the user is authorized to make this request. * @@ -29,9 +34,19 @@ class BulkClientRequest extends Request { return [ - 'ids' => 'required|bail|array', + 'ids' => ['required','bail','array',Rule::exists('clients','id')->where('company_id', auth()->user()->company()->id)], 'action' => 'in:archive,restore,delete' ]; } + + public function prepareForValidation() + { + $input = $this->all(); + + if(isset($input['ids'])) + $input['ids'] = $this->transformKeys($input['ids']); + + $this->replace($input); + } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 8aa870a9c37e..134d11ded285 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -22,7 +22,7 @@ use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Carbon\Carbon; use DateTime; -use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; use Laracasts\Presenter\PresentableTrait; @@ -500,7 +500,8 @@ class Account extends BaseModel } return $this - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + ->where('id', $this->decodePrimaryKey($value)) + ->firstOrFail(); } public function getTrialDays() diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 9633382388fa..9673ecddd3d8 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -12,6 +12,7 @@ namespace App\Models; use App\Utils\Traits\MakesHash; +use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; class Activity extends StaticModel { @@ -374,17 +375,18 @@ class Activity extends StaticModel return $this->belongsTo(Company::class); } - /** - * @return mixed - */ - public function resolveRouteBinding($value, $field = null) - { - if (is_numeric($value)) { - throw new ModelNotFoundException("Record with value {$value} not found"); - } +// /** +// * @return mixed +// */ +// public function resolveRouteBinding($value, $field = null) +// { +// if (is_numeric($value)) { +// throw new ModelNotFoundException("Record with value {$value} not found"); +// } - return $this - //->withTrashed() - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); - } -} +// return $this +// //->withTrashed() +// ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); +// } + +} \ No newline at end of file diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 366b717b3951..0573f1864890 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -174,6 +174,7 @@ class BaseModel extends Model return $this ->withTrashed() + ->company() ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); } diff --git a/app/Models/ClientContact.php b/app/Models/ClientContact.php index 23041b063104..887607bd1400 100644 --- a/app/Models/ClientContact.php +++ b/app/Models/ClientContact.php @@ -240,6 +240,7 @@ class ClientContact extends Authenticatable implements HasLocalePreference { return $this ->withTrashed() + ->company() ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); } diff --git a/app/Models/ClientGatewayToken.php b/app/Models/ClientGatewayToken.php index e2654195e7df..f040ab4ec040 100644 --- a/app/Models/ClientGatewayToken.php +++ b/app/Models/ClientGatewayToken.php @@ -70,16 +70,16 @@ class ClientGatewayToken extends BaseModel return $this->belongsTo(User::class)->withTrashed(); } - /** - * Retrieve the model for a bound value. - * - * @param mixed $value - * @param null $field - * @return Model|null - */ - public function resolveRouteBinding($value, $field = null) - { - return $this - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); - } + // /** + // * Retrieve the model for a bound value. + // * + // * @param mixed $value + // * @param null $field + // * @return Model|null + // */ + // public function resolveRouteBinding($value, $field = null) + // { + // return $this + // ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + // } } diff --git a/app/Models/Company.php b/app/Models/Company.php index 33f3f01046d7..889950bbe137 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -584,7 +584,9 @@ class Company extends BaseModel public function resolveRouteBinding($value, $field = null) { - return $this->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + return $this->where('id', $this->decodePrimaryKey($value)) + ->where('account_id', auth()->user()->account_id) + ->firstOrFail(); } public function domain() diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index 0c72d9c4a521..88ad5246896e 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -412,12 +412,12 @@ class CompanyGateway extends BaseModel return route('payment_webhook', ['company_key' => $this->company->company_key, 'company_gateway_id' => $this->hashed_id]); } - public function resolveRouteBinding($value, $field = null) - { + // public function resolveRouteBinding($value, $field = null) + // { - return $this - ->where('id', $this->decodePrimaryKey($value))->withTrashed()->firstOrFail(); - } + // return $this + // ->where('id', $this->decodePrimaryKey($value))->withTrashed()->firstOrFail(); + // } } diff --git a/app/Models/GroupSetting.php b/app/Models/GroupSetting.php index fccae8b01cbc..89faa9cc4147 100644 --- a/app/Models/GroupSetting.php +++ b/app/Models/GroupSetting.php @@ -65,16 +65,4 @@ class GroupSetting extends StaticModel return $this->morphMany(Document::class, 'documentable'); } - /** - * Retrieve the model for a bound value. - * - * @param mixed $value - * @param null $field - * @return Model|null - */ - public function resolveRouteBinding($value, $field = null) - { - return $this - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); - } } diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 159fd2c35801..cca89f79d08d 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -292,12 +292,13 @@ class Payment extends BaseModel return new PaymentService($this); } - public function resolveRouteBinding($value, $field = null) - { - return $this - ->withTrashed() - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); - } + + // public function resolveRouteBinding($value, $field = null) + // { + // return $this + // ->withTrashed() + // ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + // } public function refund(array $data) :self { diff --git a/app/Models/StaticModel.php b/app/Models/StaticModel.php index 2cf5adc4d4fc..677a351936ea 100644 --- a/app/Models/StaticModel.php +++ b/app/Models/StaticModel.php @@ -11,10 +11,14 @@ namespace App\Models; +use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; class StaticModel extends Model { + use MakesHash; + protected $casts = [ 'updated_at' => 'timestamp', 'created_at' => 'timestamp', @@ -37,4 +41,24 @@ class StaticModel extends Model return $query; } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @param null $field + * @return Model|null + */ + public function resolveRouteBinding($value, $field = null) + { + + if (is_numeric($value)) { + throw new ModelNotFoundException("Record with value {$value} not found"); + } + + return $this + ->withTrashed() + ->company() + ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + } } diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index 6d84e4b349f4..c4127396b413 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -148,7 +148,9 @@ class SystemLog extends Model } return $this - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + ->where('id', $this->decodePrimaryKey($value)) + ->company() + ->firstOrFail(); } /* diff --git a/app/Models/User.php b/app/Models/User.php index c8ebf0692297..63e94b633b67 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -193,7 +193,6 @@ class User extends Authenticatable implements MustVerifyEmail return $truth->getCompany(); } elseif (request()->header('X-API-TOKEN')) { $company_token = CompanyToken::with(['company'])->where('token', request()->header('X-API-TOKEN'))->first(); - return $company_token->company; } @@ -441,7 +440,9 @@ class User extends Authenticatable implements MustVerifyEmail { return $this ->withTrashed() - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + ->where('id', $this->decodePrimaryKey($value)) + ->where('account_id', auth()->user()->account_id) + ->firstOrFail(); } /** diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 2fc72ade957e..b4bcc27a51ab 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -145,7 +145,9 @@ class VendorContact extends Authenticatable implements HasLocalePreference { return $this ->withTrashed() - ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + ->company() + ->where('id', $this->decodePrimaryKey($value)) + ->firstOrFail(); } public function purchase_order_invitations(): \Illuminate\Database\Eloquent\Relations\HasMany diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 0a993436e417..466c5f2f8ded 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -13,7 +13,7 @@ namespace App\Providers; use App\Models\Scheduler; use App\Utils\Traits\MakesHash; -use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Route; @@ -30,7 +30,6 @@ class RouteServiceProvider extends ServiceProvider { parent::boot(); - Route::bind('task_scheduler', function ($value) { if (is_numeric($value)) { @@ -39,6 +38,7 @@ class RouteServiceProvider extends ServiceProvider return Scheduler::query() ->withTrashed() + ->company() ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); }); diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 6406414d04cb..8bc5fb4dc3c6 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -13,8 +13,13 @@ namespace Tests\Feature; use App\DataMapper\ClientSettings; use App\Factory\ClientFactory; +use App\Factory\CompanyUserFactory; use App\Http\Requests\Client\StoreClientRequest; +use App\Models\Account; use App\Models\Client; +use App\Models\Company; +use App\Models\CompanyToken; +use App\Models\User; use App\Repositories\ClientContactRepository; use App\Repositories\ClientRepository; use App\Utils\Number; @@ -53,6 +58,73 @@ class ClientApiTest extends TestCase } + public function testCrossCompanyBulkActionsFail() + { + + $account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $account->num_users = 3; + $account->save(); + + $company = Company::factory()->create([ + 'account_id' => $account->id, + ]); + + $user = User::factory()->create([ + 'account_id' => $account->id, + 'confirmation_code' => '123', + 'email' => $this->faker->safeEmail(), + ]); + + $cu = CompanyUserFactory::create($user->id, $company->id, $account->id); + $cu->is_owner = true; + $cu->is_admin = true; + $cu->is_locked = true; + $cu->permissions = '["view_client"]'; + $cu->save(); + + $different_company_token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken; + $company_token->user_id = $user->id; + $company_token->company_id = $company->id; + $company_token->account_id = $account->id; + $company_token->name = 'test token'; + $company_token->token = $different_company_token; + $company_token->is_system = true; + $company_token->save(); + + $data = [ + 'action' => 'archive', + 'ids' => [ + $this->client->id + ] + ]; + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/clients/bulk', $data) + ->assertStatus(302); + + //using existing permissions, they must pass the ->edit guard() + $this->client->fresh(); + $this->assertNull($this->client->deleted_at); + + $rules = [ + 'ids' => 'required|bail|array|exists:clients,id,company_id,'.$company->id, + 'action' => 'in:archive,restore,delete' + ]; + + $v = $this->app['validator']->make($data, $rules); + + $this->assertFalse($v->passes()); + + } + + public function testClientBulkActionValidation() { $data = [ @@ -737,17 +809,24 @@ class ClientApiTest extends TestCase public function testClientArchived() { $data = [ - 'ids' => [$this->encodePrimaryKey($this->client->id)], + 'ids' => [$this->client->hashed_id], ]; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/clients/bulk?action=archive', $data); + $response = false; - $arr = $response->json(); + try{ + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/clients/bulk?action=archive', $data); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($message); + } - $this->assertNotNull($arr['data'][0]['archived_at']); + if($response){ + $arr = $response->json(); + $this->assertNotNull($arr['data'][0]['archived_at']); + } } public function testClientRestored() diff --git a/tests/Integration/EventTest.php b/tests/Integration/EventTest.php index 58bc6211c8aa..ccf565521fd5 100644 --- a/tests/Integration/EventTest.php +++ b/tests/Integration/EventTest.php @@ -639,7 +639,6 @@ class EventTest extends TestCase ])->postJson('/api/v1/clients/', $data) ->assertStatus(200); - $arr = $response->json(); $data = [ @@ -653,7 +652,6 @@ class EventTest extends TestCase ])->putJson('/api/v1/clients/' . $arr['data']['id'], $data) ->assertStatus(200); - $data = [ 'ids' => [$arr['data']['id']], ]; diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 218f415ba5c6..8c3160c95b96 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -57,6 +57,7 @@ use App\Models\Vendor; use App\Models\VendorContact; use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesHash; +use App\Utils\TruthSource; use Illuminate\Foundation\Testing\WithoutEvents; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Cache; @@ -276,6 +277,7 @@ trait MockAccountData $this->user = $user; // auth()->login($user); + // auth()->user()->setCompany($this->company); CreateCompanyTaskStatuses::dispatchSync($this->company, $this->user); @@ -297,6 +299,11 @@ trait MockAccountData $company_token->save(); + $truth = app()->make(TruthSource::class); + $truth->setCompanyUser($company_token->first()); + $truth->setUser($this->user); + $truth->setCompany($this->company); + //todo create one token with token name TOKEN - use firstOrCreate Product::factory()->create([