From 0148d06205905995059671249bf8fa14c09a9702 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 19:08:00 -0400 Subject: [PATCH 1/8] Add user permission support --- app/Http/Controllers/AccountController.php | 8 +- app/Http/Controllers/BaseController.php | 45 +++++++- app/Http/Controllers/ClientController.php | 69 +++++++++--- app/Http/Controllers/CreditController.php | 12 +- app/Http/Controllers/ExpenseController.php | 12 +- app/Http/Controllers/InvoiceController.php | 14 ++- app/Http/Controllers/PaymentController.php | 13 ++- app/Http/Controllers/QuoteController.php | 7 +- app/Http/Controllers/TaskController.php | 14 ++- app/Http/Controllers/UserController.php | 4 + app/Http/Controllers/VendorController.php | 15 +++ app/Http/Kernel.php | 1 + app/Http/Middleware/PermissionsRequired.php | 57 ++++++++++ app/Http/routes.php | 105 ++++++++++-------- app/Libraries/Utils.php | 15 +++ app/Models/EntityModel.php | 36 ++++++ app/Models/Product.php | 4 + app/Models/TaxRate.php | 4 + app/Models/User.php | 69 +++++++++++- app/Ninja/Repositories/ClientRepository.php | 3 +- app/Ninja/Repositories/CreditRepository.php | 3 +- app/Ninja/Repositories/ExpenseRepository.php | 3 +- app/Ninja/Repositories/InvoiceRepository.php | 3 +- app/Ninja/Repositories/PaymentRepository.php | 1 + app/Ninja/Repositories/TaskRepository.php | 3 +- app/Ninja/Repositories/UserRepository.php | 3 +- app/Ninja/Repositories/VendorRepository.php | 3 +- app/Providers/AppServiceProvider.php | 46 +++++--- app/Services/ClientService.php | 44 +++++++- app/Services/DatatableService.php | 23 ++-- app/Services/ExpenseService.php | 14 ++- app/Services/InvoiceService.php | 29 ++++- app/Services/PaymentService.php | 8 ++ app/Services/RecurringInvoiceService.php | 9 ++ app/Services/TaskService.php | 14 ++- app/Services/UserService.php | 17 ++- app/Services/VendorService.php | 16 ++- ...2016_03_14_066181_add_user_permissions.php | 33 ++++++ resources/lang/en/texts.php | 8 ++ .../views/accounts/user_details.blade.php | 4 +- resources/views/clients/show.blade.php | 15 ++- resources/views/header.blade.php | 24 ++-- resources/views/list.blade.php | 4 +- resources/views/user_account.blade.php | 13 ++- resources/views/users/edit.blade.php | 31 +++++- 45 files changed, 719 insertions(+), 159 deletions(-) create mode 100644 app/Http/Middleware/PermissionsRequired.php create mode 100644 database/migrations/2016_03_14_066181_add_user_permissions.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index abefe0b5f745..755932691d18 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -137,8 +137,6 @@ class AccountController extends BaseController if ($section == ACCOUNT_COMPANY_DETAILS) { return self::showCompanyDetails(); - } elseif ($section == ACCOUNT_USER_DETAILS) { - return self::showUserDetails(); } elseif ($section == ACCOUNT_LOCALIZATION) { return self::showLocalization(); } elseif ($section == ACCOUNT_PAYMENTS) { @@ -232,7 +230,7 @@ class AccountController extends BaseController return View::make('accounts.details', $data); } - private function showUserDetails() + public function showUserDetails() { $oauthLoginUrls = []; foreach (AuthService::$providers as $provider) { @@ -467,8 +465,6 @@ class AccountController extends BaseController { if ($section === ACCOUNT_COMPANY_DETAILS) { return AccountController::saveDetails(); - } elseif ($section === ACCOUNT_USER_DETAILS) { - return AccountController::saveUserDetails(); } elseif ($section === ACCOUNT_LOCALIZATION) { return AccountController::saveLocalization(); } elseif ($section === ACCOUNT_NOTIFICATIONS) { @@ -839,7 +835,7 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS); } - private function saveUserDetails() + public function saveUserDetails() { $user = Auth::user(); $rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id']; diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index ee7f62686fb4..5124097636a9 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -1,10 +1,14 @@ layout = View::make($this->layout); } } - - /* - public function __construct() - { - $this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put'))); + + protected function checkViewPermission($object, &$response = null){ + if(!$object->canView()){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkEditPermission($object, &$response = null){ + if(!$object->canEdit()){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkCreatePermission(&$response = null){ + if(!call_user_func(array($this->model, 'canCreate'))){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkUpdatePermission($input, &$response = null){ + $creating = empty($input['public_id']) || $input['public_id'] == '-1'; + + if($creating){ + return $this->checkCreatePermission($response); + } + else{ + $object = call_user_func(array($this->model, 'scope'), $input['public_id'])->firstOrFail(); + return $this->checkEditPermission($object, $response); + } } - */ } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 163d75f622ba..7e1c01de9e6e 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -20,6 +20,9 @@ use App\Models\Size; use App\Models\PaymentTerm; use App\Models\Industry; use App\Models\Currency; +use App\Models\Payment; +use App\Models\Credit; +use App\Models\Expense; use App\Models\Country; use App\Models\Task; use App\Ninja\Repositories\ClientRepository; @@ -32,6 +35,7 @@ class ClientController extends BaseController { protected $clientService; protected $clientRepo; + protected $model = 'App\Models\Client'; public function __construct(ClientRepository $clientRepo, ClientService $clientService) { @@ -77,7 +81,13 @@ class ClientController extends BaseController */ public function store(CreateClientRequest $request) { - $client = $this->clientService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $client = $this->clientService->save($data); Session::flash('message', trans('texts.created_client')); @@ -93,22 +103,36 @@ class ClientController extends BaseController public function show($publicId) { $client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail(); + + if(!$this->checkViewPermission($client, $response)){ + return $response; + } + Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); - $actionLinks = [ - ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id] - ]; - - if (Utils::isPro()) { - array_push($actionLinks, ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]); + $actionLinks = []; + if(Task::canCreate()){ + $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]; + } + if (Utils::isPro() && Invoice::canCreate()) { + $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]; + } + + if(!empty($actionLinks)){ + $actionLinks[] = \DropdownButton::DIVIDER; + } + + if(Payment::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id]; + } + + if(Credit::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]; + } + + if(Expense::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]; } - - array_push($actionLinks, - \DropdownButton::DIVIDER, - ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id], - ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id], - ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id] - ); $data = array( 'actionLinks' => $actionLinks, @@ -132,6 +156,10 @@ class ClientController extends BaseController */ public function create() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) { return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]); } @@ -157,6 +185,11 @@ class ClientController extends BaseController public function edit($publicId) { $client = Client::scope($publicId)->with('contacts')->firstOrFail(); + + if(!$this->checkEditPermission($client, $response)){ + return $response; + } + $data = [ 'client' => $client, 'method' => 'PUT', @@ -199,7 +232,13 @@ class ClientController extends BaseController */ public function update(UpdateClientRequest $request) { - $client = $this->clientService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $client = $this->clientService->save($data); Session::flash('message', trans('texts.updated_client')); diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 5a4b3011ad50..26085c3d6b32 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -17,10 +17,11 @@ class CreditController extends BaseController { protected $creditRepo; protected $creditService; + protected $model = 'App\Models\Credit'; public function __construct(CreditRepository $creditRepo, CreditService $creditService) { - //parent::__construct(); + // parent::__construct(); $this->creditRepo = $creditRepo; $this->creditService = $creditService; @@ -56,6 +57,10 @@ class CreditController extends BaseController public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + $data = array( 'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, //'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId, @@ -72,6 +77,11 @@ class CreditController extends BaseController public function edit($publicId) { $credit = Credit::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($credit, $response)){ + return $response; + } + $credit->credit_date = Utils::fromSqlDate($credit->credit_date); $data = array( diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 2d4294d540a1..eb9fa0c437ee 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -25,10 +25,11 @@ class ExpenseController extends BaseController // Expenses protected $expenseRepo; protected $expenseService; + protected $model = 'App\Models\Expense'; public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService) { - //parent::__construct(); + // parent::__construct(); $this->expenseRepo = $expenseRepo; $this->expenseService = $expenseService; @@ -70,6 +71,10 @@ class ExpenseController extends BaseController public function create($vendorPublicId = null, $clientPublicId = null) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if($vendorPublicId != 0) { $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); } else { @@ -95,6 +100,11 @@ class ExpenseController extends BaseController public function edit($publicId) { $expense = Expense::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($expense, $response)){ + return $response; + } + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); $actions = []; diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index fbbdc7c08e6f..089953f84c67 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -34,10 +34,11 @@ class InvoiceController extends BaseController protected $clientRepo; protected $invoiceService; protected $recurringInvoiceService; + protected $model = 'App\Models\Invoice'; public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService) { - //parent::__construct(); + // parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; @@ -90,6 +91,11 @@ class InvoiceController extends BaseController ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items') ->withTrashed() ->firstOrFail(); + + if(!$this->checkEditPermission($invoice, $response)){ + return $response; + } + $entityType = $invoice->getEntityType(); $contactIds = DB::table('invitations') @@ -206,7 +212,11 @@ class InvoiceController extends BaseController public function create($clientPublicId = 0, $isRecurring = false) { - $account = Auth::user()->account; + if(!$this->checkCreatePermission($response)){ + return $response; + } + + $account = Auth::user()->account; $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $clientId = null; diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 24c4005f1d21..7dc13ec6ed56 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -30,9 +30,11 @@ use App\Http\Requests\UpdatePaymentRequest; class PaymentController extends BaseController { + protected $model = 'App\Models\Payment'; + public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService) { - //parent::__construct(); + // parent::__construct(); $this->paymentRepo = $paymentRepo; $this->invoiceRepo = $invoiceRepo; @@ -66,6 +68,10 @@ class PaymentController extends BaseController public function create($clientPublicId = 0, $invoicePublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + $invoices = Invoice::scope() ->where('is_recurring', '=', false) ->where('is_quote', '=', false) @@ -92,6 +98,11 @@ class PaymentController extends BaseController public function edit($publicId) { $payment = Payment::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($payment, $response)){ + return $response; + } + $payment->payment_date = Utils::fromSqlDate($payment->payment_date); $data = array( diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 882acb039e08..7777c5a9d006 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -33,10 +33,11 @@ class QuoteController extends BaseController protected $invoiceRepo; protected $clientRepo; protected $invoiceService; + protected $model = 'App\Models\Invoice'; public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService) { - //parent::__construct(); + // parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; @@ -78,6 +79,10 @@ class QuoteController extends BaseController public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (!Utils::isPro()) { return Redirect::to('/invoices/create'); } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index e09ed50b4ba5..ea3001dfc538 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -22,10 +22,11 @@ class TaskController extends BaseController { protected $taskRepo; protected $taskService; + protected $model = 'App\Models\Task'; public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService) { - //parent::__construct(); + // parent::__construct(); $this->taskRepo = $taskRepo; $this->invoiceRepo = $invoiceRepo; @@ -67,6 +68,10 @@ class TaskController extends BaseController */ public function store() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + return $this->save(); } @@ -84,6 +89,9 @@ class TaskController extends BaseController */ public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } $this->checkTimezone(); $data = [ @@ -113,6 +121,10 @@ class TaskController extends BaseController $task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail(); + if(!$this->checkEditPermission($task, $response)){ + return $response; + } + $actions = []; if ($task->invoice) { $actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index ee3f9d65540c..0e5a8dd70dd6 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -192,6 +192,8 @@ class UserController extends BaseController $user->last_name = trim(Input::get('last_name')); $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); + $user->is_admin = boolval(Input::get('is_admin')); + $user->permissions = Input::get('permissions'); } else { $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id) ->orderBy('public_id', 'DESC')->first(); @@ -202,10 +204,12 @@ class UserController extends BaseController $user->last_name = trim(Input::get('last_name')); $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); + $user->is_admin = boolval(Input::get('is_admin')); $user->registered = true; $user->password = str_random(RANDOM_KEY_LENGTH); $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); $user->public_id = $lastUser->public_id + 1; + $user->permissions = Input::get('permissions'); } $user->save(); diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index 5d25f29215d6..bab1479b446e 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -30,6 +30,7 @@ class VendorController extends BaseController { protected $vendorService; protected $vendorRepo; + protected $model = 'App\Models\Vendor'; public function __construct(VendorRepository $vendorRepo, VendorService $vendorService) { @@ -92,6 +93,11 @@ class VendorController extends BaseController public function show($publicId) { $vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail(); + + if(!$this->checkViewPermission($vendor, $response)){ + return $response; + } + Utils::trackViewed($vendor->getDisplayName(), 'vendor'); $actionLinks = [ @@ -119,6 +125,10 @@ class VendorController extends BaseController */ public function create() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) { return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]); } @@ -144,6 +154,11 @@ class VendorController extends BaseController public function edit($publicId) { $vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail(); + + if(!$this->checkEditPermission($vendor, $response)){ + return $response; + } + $data = [ 'vendor' => $vendor, 'method' => 'PUT', diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 72ffce9c8bf3..1142338b200e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -28,6 +28,7 @@ class Kernel extends HttpKernel { protected $routeMiddleware = [ 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', + 'permissions.required' => 'App\Http\Middleware\PermissionsRequired', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'api' => 'App\Http\Middleware\ApiCheck', ]; diff --git a/app/Http/Middleware/PermissionsRequired.php b/app/Http/Middleware/PermissionsRequired.php new file mode 100644 index 000000000000..af0e0015a37f --- /dev/null +++ b/app/Http/Middleware/PermissionsRequired.php @@ -0,0 +1,57 @@ + [action => permission] + */ + static protected $actions = []; + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next, $guard = 'user') + { + // Get the current route. + $route = $request->route(); + + // Get the current route actions. + $actions = $route->getAction(); + + // Check if we have any permissions to check the user has. + if ($permissions = !empty($actions['permissions']) ? $actions['permissions'] : null) + { + if(!Auth::user($guard)->hasPermission($permissions, !empty($actions['permissions_require_all']))){ + return response('Unauthorized.', 401); + } + } + + // Check controller permissions + $action = explode('@', $request->route()->getActionName()); + if(isset(static::$actions[$action[0]]) && isset(static::$actions[$action[0]][$action[1]])) { + $controller_permissions = static::$actions[$action[0]][$action[1]]; + if(!Auth::user($guard)->hasPermission($controller_permissions)){ + return response('Unauthorized.', 401); + } + } + + return $next($request); + } + + /** + * add a controller's action permission + * + * @param \App\Http\Controllers\Controller $controller + * @param array $permissions + */ + public static function addPermission(\App\Http\Controllers\Controller $controller, $permissions) + { + static::$actions[get_class($controller)] = $permissions; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 0c9aef7c1a6a..75e7ce9ab2ad 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -104,21 +104,9 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); - - Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); - Route::resource('users', 'UserController'); - Route::post('users/bulk', 'UserController@bulk'); - Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); - Route::get('start_trial', 'AccountController@startTrial'); - Route::get('restore_user/{user_id}', 'UserController@restoreUser'); - Route::post('users/change_password', 'UserController@changePassword'); - Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); - Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); - Route::get('/manage_companies', 'UserController@manageCompanies'); - - Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); - Route::resource('tokens', 'TokenController'); - Route::post('tokens/bulk', 'TokenController@bulk'); + + Route::get('settings/user_details', 'AccountController@showUserDetails'); + Route::post('settings/user_details', 'AccountController@saveUserDetails'); Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable')); Route::resource('products', 'ProductController'); @@ -128,39 +116,6 @@ Route::group(['middleware' => 'auth:user'], function() { Route::resource('tax_rates', 'TaxRateController'); Route::post('tax_rates/bulk', 'TaxRateController@bulk'); - Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); - Route::get('settings/data_visualizations', 'ReportController@d3'); - Route::get('settings/charts_and_reports', 'ReportController@showReports'); - Route::post('settings/charts_and_reports', 'ReportController@showReports'); - - Route::post('settings/cancel_account', 'AccountController@cancelAccount'); - Route::post('settings/company_details', 'AccountController@updateDetails'); - Route::get('settings/{section?}', 'AccountController@showSection'); - Route::post('settings/{section?}', 'AccountController@doSection'); - - //Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable')); - //Route::resource('payment_terms', 'PaymentTermController'); - //Route::post('payment_terms/bulk', 'PaymentTermController@bulk'); - - Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); - Route::post('user/setTheme', 'UserController@setTheme'); - Route::post('remove_logo', 'AccountController@removeLogo'); - Route::post('account/go_pro', 'AccountController@enableProPlan'); - - Route::post('/export', 'ExportController@doExport'); - Route::post('/import', 'ImportController@doImport'); - Route::post('/import_csv', 'ImportController@doImportCSV'); - - Route::resource('gateways', 'AccountGatewayController'); - Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); - Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); - - Route::resource('bank_accounts', 'BankAccountController'); - Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable')); - Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); - Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); - Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); - Route::resource('clients', 'ClientController'); Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); @@ -222,6 +177,59 @@ Route::group(['middleware' => 'auth:user'], function() { Route::post('expenses/bulk', 'ExpenseController@bulk'); }); +Route::group([ + 'middleware' => ['auth:user', 'permissions.required'], + 'permissions' => 'admin', +], function() { + Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); + Route::resource('users', 'UserController'); + Route::post('users/bulk', 'UserController@bulk'); + Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); + Route::get('start_trial', 'AccountController@startTrial'); + Route::get('restore_user/{user_id}', 'UserController@restoreUser'); + Route::post('users/change_password', 'UserController@changePassword'); + Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); + Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); + Route::get('/manage_companies', 'UserController@manageCompanies'); + + Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); + Route::resource('tokens', 'TokenController'); + Route::post('tokens/bulk', 'TokenController@bulk'); + + Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); + Route::get('settings/data_visualizations', 'ReportController@d3'); + Route::get('settings/charts_and_reports', 'ReportController@showReports'); + Route::post('settings/charts_and_reports', 'ReportController@showReports'); + + Route::post('settings/cancel_account', 'AccountController@cancelAccount'); + Route::post('settings/company_details', 'AccountController@updateDetails'); + Route::get('settings/{section?}', 'AccountController@showSection'); + Route::post('settings/{section?}', 'AccountController@doSection'); + + //Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable')); + //Route::resource('payment_terms', 'PaymentTermController'); + //Route::post('payment_terms/bulk', 'PaymentTermController@bulk'); + + Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); + Route::post('user/setTheme', 'UserController@setTheme'); + Route::post('remove_logo', 'AccountController@removeLogo'); + Route::post('account/go_pro', 'AccountController@enableProPlan'); + + Route::post('/export', 'ExportController@doExport'); + Route::post('/import', 'ImportController@doImport'); + Route::post('/import_csv', 'ImportController@doImportCSV'); + + Route::resource('gateways', 'AccountGatewayController'); + Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); + Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); + + Route::resource('bank_accounts', 'BankAccountController'); + Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable')); + Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); + Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); + Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); +}); + // Route groups for API Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() { @@ -604,6 +612,7 @@ if (!defined('CONTACT_EMAIL')) { define('USER_STATE_PENDING', 'pending'); define('USER_STATE_DISABLED', 'disabled'); define('USER_STATE_ADMIN', 'admin'); + define('USER_STATE_OWNER', 'owner'); define('API_SERIALIZER_ARRAY', 'array'); define('API_SERIALIZER_JSON', 'json'); diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index fcb210b0d7ad..cfcc97c00114 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -118,6 +118,21 @@ class Utils return Auth::check() && Auth::user()->isPro(); } + public static function isAdmin() + { + return Auth::check() && Auth::user()->is_admin; + } + + public static function hasPermission($permission, $requireAll = false) + { + return Auth::check() && Auth::user()->hasPermission($permission, $requireAll); + } + + public static function hasAllPermissions($permission) + { + return Auth::check() && Auth::user()->hasPermissions($permission); + } + public static function isTrial() { return Auth::check() && Auth::user()->isTrial(); diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index b57eded2fb7f..6006bcbd4071 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -113,4 +113,40 @@ class EntityModel extends Eloquent $name = $parts[count($parts)-1]; return strtolower($name) . '_id'; } + + public static function canCreate() { + return Auth::user()->hasPermission('create_all'); + } + + public function canEdit() { + return static::canEditItem($this); + } + + public static function canEditItem($item) { + return Auth::user()->hasPermission('edit_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id); + } + + public static function canEditItemById($item_id) { + if(Auth::user()->hasPermission('edit_all')) { + return true; + } + + return static::whereId($item_id)->first()->user_id == Auth::user()->id; + } + + public function canView() { + return static::canEdit($this); + } + + public static function canViewItem($item) { + return Auth::user()->hasPermission('view_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id); + } + + public static function canViewItemById($item_id) { + if(Auth::user()->hasPermission('view_all')) { + return true; + } + + return static::whereId($item_id)->first()->user_id == Auth::user()->id; + } } diff --git a/app/Models/Product.php b/app/Models/Product.php index 6f03676618e1..e00d0feebec8 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -21,4 +21,8 @@ class Product extends EntityModel { return $this->belongsTo('App\Models\TaxRate'); } + + public function canEdit() { + return Auth::user()->hasPermission('admin'); + } } diff --git a/app/Models/TaxRate.php b/app/Models/TaxRate.php index 7adf6f768b5f..15c39a7572a0 100644 --- a/app/Models/TaxRate.php +++ b/app/Models/TaxRate.php @@ -16,4 +16,8 @@ class TaxRate extends EntityModel { return ENTITY_TAX_RATE; } + + public function canEdit() { + return Auth::user()->hasPermission('admin'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 9f66f0d2af8f..1b2ae7815a36 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,7 +14,12 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Database\Eloquent\SoftDeletes; class User extends Model implements AuthenticatableContract, CanResetPasswordContract { - + public static $all_permissions = array( + 'create_all' => 0b0001, + 'view_all' => 0b0010, + 'edit_all' => 0b0100, + ); + use Authenticatable, CanResetPassword; /** @@ -253,7 +258,69 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon && $this->email != $this->getOriginal('email') && $this->getOriginal('confirmed'); } + + + + /** + * Set the permissions attribute on the model. + * + * @param mixed $value + * @return $this + */ + protected function setPermissionsAttribute($value){ + if(empty($value)) { + $this->attributes['permissions'] = 0; + } else { + $bitmask = 0; + foreach($value as $permission){ + $bitmask = $bitmask | static::$all_permissions[$permission]; + } + $this->attributes['permissions'] = $bitmask; + } + + return $this; + } + + /** + * Expands the value of the permissions attribute + * + * @param mixed $value + * @return mixed + */ + protected function getPermissionsAttribute($value){ + $permissions = array(); + foreach(static::$all_permissions as $permission => $bitmask){ + if(($value & $bitmask) == $bitmask) { + $permissions[$permission] = $permission; + } + } + + return $permissions; + } + + /** + * Checks to see if the user has the required permission + * + * @param mixed $permission Either a single permission or an array of possible permissions + * @param boolean True to require all permissions, false to require only one + * @return boolean + */ + public function hasPermission($permission, $requireAll = false){ + if ($this->is_admin) { + return true; + } else if(is_string($permission)){ + return !empty($this->permissions[$permission]); + } else if(is_array($permission)) { + if($requireAll){ + return count(array_diff($permission, $this->permissions)) == 0; + } else { + return count(array_intersect($permission, $this->permissions)) > 0; + } + } + + return false; + } } User::updating(function ($user) { diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index 2e8ce31b5c1c..9df81663a5e5 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -46,7 +46,8 @@ class ClientRepository extends BaseRepository 'clients.work_phone', 'contacts.email', 'clients.deleted_at', - 'clients.is_deleted' + 'clients.is_deleted', + 'clients.user_id' ); if (!\Session::get('show_trash:client')) { diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php index 1c33cb19e41d..7252a7ee0e4f 100644 --- a/app/Ninja/Repositories/CreditRepository.php +++ b/app/Ninja/Repositories/CreditRepository.php @@ -37,7 +37,8 @@ class CreditRepository extends BaseRepository 'contacts.email', 'credits.private_notes', 'credits.deleted_at', - 'credits.is_deleted' + 'credits.is_deleted', + 'credits.user_id' ); if ($clientPublicId) { diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index cfd312622ad3..4d5f0d590af4 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -40,7 +40,8 @@ class ExpenseRepository extends BaseRepository 'expenses.public_id', 'expenses.deleted_at', 'expenses.should_be_invoiced', - 'expenses.created_at' + 'expenses.created_at', + 'expenses.user_id' ); return $query; diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index f028acdf930a..e577d93ac58a 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -65,7 +65,8 @@ class InvoiceRepository extends BaseRepository 'invoices.quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted', - 'invoices.partial' + 'invoices.partial', + 'invoices.user_id' ); if (!\Session::get('show_trash:'.$entityType)) { diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 15f5007251c3..838def01d338 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -47,6 +47,7 @@ class PaymentRepository extends BaseRepository 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', + 'payments.user_id', 'invoices.is_deleted as invoice_is_deleted', 'gateways.name as gateway_name' ); diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 47a052378ff1..8f913c705a0d 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -38,7 +38,8 @@ class TaskRepository 'invoices.public_id as invoice_public_id', 'tasks.is_running', 'tasks.time_log', - 'tasks.created_at' + 'tasks.created_at', + 'tasks.user_id' ); if ($clientPublicId) { diff --git a/app/Ninja/Repositories/UserRepository.php b/app/Ninja/Repositories/UserRepository.php index 5675a161a6ff..ca5878b0c188 100644 --- a/app/Ninja/Repositories/UserRepository.php +++ b/app/Ninja/Repositories/UserRepository.php @@ -22,7 +22,7 @@ class UserRepository extends BaseRepository $query->where('users.deleted_at', '=', null); } - $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at'); + $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at', 'users.is_admin', 'users.permissions'); return $query; } @@ -34,5 +34,4 @@ class UserRepository extends BaseRepository return $user; } - } diff --git a/app/Ninja/Repositories/VendorRepository.php b/app/Ninja/Repositories/VendorRepository.php index 81fc8fc7f47b..df885f62e12e 100644 --- a/app/Ninja/Repositories/VendorRepository.php +++ b/app/Ninja/Repositories/VendorRepository.php @@ -42,7 +42,8 @@ class VendorRepository extends BaseRepository 'vendors.city', 'vendor_contacts.email', 'vendors.deleted_at', - 'vendors.is_deleted' + 'vendors.is_deleted', + 'vendors.user_id' ); if (!\Session::get('show_trash:vendor')) { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e5fcf4540f10..3cd691098747 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,9 @@ use Form; use URL; use Request; use Validator; +use App\Models\Credit; +use App\Models\Invoice; +use App\Models\Vendor; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { @@ -46,31 +49,38 @@ class AppServiceProvider extends ServiceProvider { $class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*settings*') ? ' active' : ''; $str = ''; + $str .= ''; return $str; }); diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 21242da54a6b..662fc8eab9e8 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -4,6 +4,12 @@ use Utils; use URL; use Auth; use App\Services\BaseService; +use App\Models\Client; +use App\Models\Invoice; +use App\Models\Credit; +use App\Models\Expense; +use App\Models\Payment; +use App\Models\Task; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\NinjaRepository; @@ -37,6 +43,10 @@ class ClientService extends BaseService { $query = $this->clientRepo->find($search); + if(!Utils::hasPermission('view_all')){ + $query->where('clients.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_CLIENT, $query); } @@ -89,19 +99,33 @@ class ClientService extends BaseService trans('texts.edit_client'), function ($model) { return URL::to("clients/{$model->public_id}/edit"); + }, + function ($model) { + return Client::canEditItem($model); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return Client::canEditItem($model) && (Task::canCreate() || Invoice::canCreate()); } ], - [], [ trans('texts.new_task'), function ($model) { return URL::to("tasks/create/{$model->public_id}"); + }, + function ($model) { + return Task::canCreate(); } ], [ trans('texts.new_invoice'), function ($model) { return URL::to("invoices/create/{$model->public_id}"); + }, + function ($model) { + return Invoice::canCreate(); } ], [ @@ -110,26 +134,40 @@ class ClientService extends BaseService return URL::to("quotes/create/{$model->public_id}"); }, function ($model) { - return Auth::user()->isPro(); + return Auth::user()->isPro() && Invoice::canCreate(); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return (Task::canCreate() || Invoice::canCreate()) && (Payment::canCreate() || Credit::canCreate() || Expense::canCreate()); } ], - [], [ trans('texts.enter_payment'), function ($model) { return URL::to("payments/create/{$model->public_id}"); + }, + function ($model) { + return Payment::canCreate(); } ], [ trans('texts.enter_credit'), function ($model) { return URL::to("credits/create/{$model->public_id}"); + }, + function ($model) { + return Credit::canCreate(); } ], [ trans('texts.enter_expense'), function ($model) { return URL::to("expenses/create/0/{$model->public_id}"); + }, + function ($model) { + return Expense::canCreate(); } ] ]; diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 285efe717aef..456c619aef04 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -3,6 +3,7 @@ use HtmlString; use Utils; use Datatable; +use Auth; class DatatableService { @@ -45,6 +46,8 @@ class DatatableService $hasAction = false; $str = '
'; + $can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id); + if (property_exists($model, 'is_deleted') && $model->is_deleted) { $str .= ''; } elseif ($model->deleted_at && $model->deleted_at !== '0000-00-00') { @@ -70,9 +73,15 @@ class DatatableService } list($value, $url, $visible) = $action; if ($visible($model)) { - $str .= "
  • {$value}
  • "; - $lastIsDivider = false; - $hasAction = true; + if($value == '--divider--'){ + $str .= "
  • "; + $lastIsDivider = true; + } + else { + $str .= "
  • {$value}
  • "; + $hasAction = true; + $lastIsDivider = false; + } } } elseif ( ! $lastIsDivider) { $str .= "
  • "; @@ -84,20 +93,20 @@ class DatatableService return ''; } - if ( ! $lastIsDivider) { + if ( $can_edit && ! $lastIsDivider) { $str .= "
  • "; } - if ($entityType != ENTITY_USER || $model->public_id) { + if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) { $str .= "
  • public_id})\">" . trans("texts.archive_{$entityType}") . "
  • "; } - } else { + } else if($can_edit) { $str .= "
  • public_id})\">" . trans("texts.restore_{$entityType}") . "
  • "; } - if (property_exists($model, 'is_deleted') && !$model->is_deleted) { + if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) { $str .= "
  • public_id})\">" . trans("texts.delete_{$entityType}") . "
  • "; } diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index 9f094240d09f..e2150ac96829 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -1,10 +1,13 @@ expenseRepo->find($search); + if(!Utils::hasPermission('view_all')){ + $query->where('expenses.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_EXPENSE, $query); } @@ -151,6 +158,9 @@ class ExpenseService extends BaseService trans('texts.edit_expense'), function ($model) { return URL::to("expenses/{$model->public_id}/edit") ; + }, + function ($model) { + return Expense::canEditItem($model); } ], [ @@ -159,7 +169,7 @@ class ExpenseService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_public_id; + return $model->invoice_public_id && Invoice::canEditItemById($model->invoice_public_id); } ], [ @@ -168,7 +178,7 @@ class ExpenseService extends BaseService return "javascript:invoiceEntity({$model->public_id})"; }, function ($model) { - return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); + return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate(); } ], ]; diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index 522c1a5dffc3..aea19ffc0803 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -8,6 +8,8 @@ use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Events\QuoteInvitationWasApproved; use App\Models\Invitation; +use App\Models\Invoice; +use App\Models\Payment; class InvoiceService extends BaseService { @@ -109,6 +111,10 @@ class InvoiceService extends BaseService $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false); + if(!Utils::hasPermission('view_all')){ + $query->where('invoices.user_id', '=', Auth::user()->id); + } + return $this->createDatatable($entityType, $query, !$clientPublicId); } @@ -174,12 +180,18 @@ class InvoiceService extends BaseService trans("texts.edit_{$entityType}"), function ($model) use ($entityType) { return URL::to("{$entityType}s/{$model->public_id}/edit"); + }, + function ($model) { + return Invoice::canEditItem($model); } ], [ trans("texts.clone_{$entityType}"), function ($model) use ($entityType) { return URL::to("{$entityType}s/{$model->public_id}/clone"); + }, + function ($model) { + return Invoice::canCreate(); } ], [ @@ -188,14 +200,19 @@ class InvoiceService extends BaseService return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}"); } ], - [], + [ + '--divider--', function(){return false;}, + function ($model) { + return Invoice::canEditItem($model) || Payment::canCreate(); + } + ], [ trans("texts.mark_sent"), function ($model) { return "javascript:markEntity({$model->public_id})"; }, function ($model) { - return $model->invoice_status_id < INVOICE_STATUS_SENT; + return $model->invoice_status_id < INVOICE_STATUS_SENT && Invoice::canEditItem($model); } ], [ @@ -204,7 +221,7 @@ class InvoiceService extends BaseService return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->balance > 0; + return $entityType == ENTITY_INVOICE && $model->balance > 0 && Payment::canCreate(); } ], [ @@ -213,7 +230,7 @@ class InvoiceService extends BaseService return URL::to("quotes/{$model->quote_id}/edit"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->quote_id; + return $entityType == ENTITY_INVOICE && $model->quote_id && Invoice::canEditItem($model); } ], [ @@ -222,7 +239,7 @@ class InvoiceService extends BaseService return URL::to("invoices/{$model->quote_invoice_id}/edit"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && $model->quote_invoice_id; + return $entityType == ENTITY_QUOTE && $model->quote_invoice_id && Invoice::canEditItem($model); } ], [ @@ -231,7 +248,7 @@ class InvoiceService extends BaseService return "javascript:convertEntity({$model->public_id})"; }, function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id; + return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Invoice::canEditItem($model); } ] ]; diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 83429f5b2068..10db86800628 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -1,6 +1,7 @@ paymentRepo->find($clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('payments.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId); } @@ -339,6 +344,9 @@ class PaymentService extends BaseService trans('texts.edit_payment'), function ($model) { return URL::to("payments/{$model->public_id}/edit"); + }, + function ($model) { + return Payment::canEditItem($model); } ] ]; diff --git a/app/Services/RecurringInvoiceService.php b/app/Services/RecurringInvoiceService.php index bd23d2a5e048..2786ccfc6869 100644 --- a/app/Services/RecurringInvoiceService.php +++ b/app/Services/RecurringInvoiceService.php @@ -1,7 +1,9 @@ invoiceRepo->getRecurringInvoices($accountId, $clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('invoices.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_RECURRING_INVOICE, $query, !$clientPublicId); } @@ -66,6 +72,9 @@ class RecurringInvoiceService extends BaseService trans('texts.edit_invoice'), function ($model) { return URL::to("invoices/{$model->public_id}/edit"); + }, + function ($model) { + return Invoice::canEditItem($model); } ] ]; diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index 59f8fd95feb0..ae575e3b2a7b 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -1,8 +1,10 @@ taskRepo->find($clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('tasks.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_TASK, $query, !$clientPublicId); } @@ -82,7 +88,7 @@ class TaskService extends BaseService return URL::to('tasks/'.$model->public_id.'/edit'); }, function ($model) { - return !$model->deleted_at || $model->deleted_at == '0000-00-00'; + return (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Task::canEditItem($model); } ], [ @@ -91,7 +97,7 @@ class TaskService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_number; + return $model->invoice_number && Invoice::canEditItemById($model->invoice_number); } ], [ @@ -100,7 +106,7 @@ class TaskService extends BaseService return "javascript:stopTask({$model->public_id})"; }, function ($model) { - return $model->is_running; + return $model->is_running && Task::canEditItem($model); } ], [ @@ -109,7 +115,7 @@ class TaskService extends BaseService return "javascript:invoiceEntity({$model->public_id})"; }, function ($model) { - return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); + return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate(); } ] ]; diff --git a/app/Services/UserService.php b/app/Services/UserService.php index fcc022003658..8e31b4c30d2d 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -53,11 +53,15 @@ class UserService extends BaseService 'confirmed', function ($model) { if (!$model->public_id) { - return self::getStatusLabel(USER_STATE_ADMIN); + return self::getStatusLabel(USER_STATE_OWNER); } elseif ($model->deleted_at) { return self::getStatusLabel(USER_STATE_DISABLED); } elseif ($model->confirmed) { - return self::getStatusLabel(USER_STATE_ACTIVE); + if($model->is_admin){ + return self::getStatusLabel(USER_STATE_ADMIN); + } else { + return self::getStatusLabel(USER_STATE_ACTIVE); + } } else { return self::getStatusLabel(USER_STATE_PENDING); } @@ -96,17 +100,20 @@ class UserService extends BaseService $class = 'default'; switch ($state) { case USER_STATE_PENDING: - $class = 'info'; + $class = 'default'; break; case USER_STATE_ACTIVE: - $class = 'primary'; + $class = 'info'; break; case USER_STATE_DISABLED: $class = 'warning'; break; - case USER_STATE_ADMIN: + case USER_STATE_OWNER: $class = 'success'; break; + case USER_STATE_ADMIN: + $class = 'primary'; + break; } return "

    $label

    "; } diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index cd2dcf8d1844..44e27ce44f2a 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -3,6 +3,8 @@ use Utils; use URL; use Auth; +use App\Models\Vendor; +use App\Models\Expense; use App\Services\BaseService; use App\Ninja\Repositories\VendorRepository; use App\Ninja\Repositories\NinjaRepository; @@ -83,13 +85,25 @@ class VendorService extends BaseService trans('texts.edit_vendor'), function ($model) { return URL::to("vendors/{$model->public_id}/edit"); + }, + function ($model) { + return Vendor::canEditItem($model); } ], - [], + [ + '--divider--', function(){return false;}, + function ($model) { + return Vendor::canEditItem($model) && Expense::canCreate(); + } + + ], [ trans('texts.enter_expense'), function ($model) { return URL::to("expenses/create/{$model->public_id}"); + }, + function ($model) { + return Expense::canCreate(); } ] ]; diff --git a/database/migrations/2016_03_14_066181_add_user_permissions.php b/database/migrations/2016_03_14_066181_add_user_permissions.php new file mode 100644 index 000000000000..2cdd5994825d --- /dev/null +++ b/database/migrations/2016_03_14_066181_add_user_permissions.php @@ -0,0 +1,33 @@ +boolean('is_admin')->default(true); + $table->unsignedInteger('permissions')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function($table) { + $table->dropColumn('is_admin'); + $table->dropColumn('permissions'); + }); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index e587699b927f..217841af7c05 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1063,6 +1063,14 @@ $LANG = array( 'invalid_expiry' => 'The expiration date is not valid.', 'invalid_cvv' => 'The CVV is not valid.', + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings, and view and modify all data', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View All clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + ); return $LANG; diff --git a/resources/views/accounts/user_details.blade.php b/resources/views/accounts/user_details.blade.php index 57bfdb8eb5a4..c9426a8b3bf4 100644 --- a/resources/views/accounts/user_details.blade.php +++ b/resources/views/accounts/user_details.blade.php @@ -21,7 +21,9 @@ {{ Former::populateField('referral_code', true) }} @endif - @include('accounts.nav', ['selected' => ACCOUNT_USER_DETAILS]) + @if (Utils::isAdmin()) + @include('accounts.nav', ['selected' => ACCOUNT_USER_DETAILS]) + @endif
    diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index ee7ac115dfc4..3166f9b800da 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -43,8 +43,11 @@ @endif @if ($client->trashed()) - {!! Button::primary(trans('texts.restore_client'))->withAttributes(['onclick' => 'onRestoreClick()']) !!} + @if ($client->canEdit()) + {!! Button::primary(trans('texts.restore_client'))->withAttributes(['onclick' => 'onRestoreClick()']) !!} + @endif @else + @if ($client->canEdit()) {!! DropdownButton::normal(trans('texts.edit_client')) ->withAttributes(['class'=>'normalDropDown']) ->withContents([ @@ -52,10 +55,12 @@ ['label' => trans('texts.delete_client'), 'url' => "javascript:onDeleteClick()"], ] )->split() !!} - - {!! DropdownButton::primary(trans('texts.new_invoice')) - ->withAttributes(['class'=>'primaryDropDown']) - ->withContents($actionLinks)->split() !!} + @endif + @if (\App\Models\Invoice::canCreate()) + {!! DropdownButton::primary(trans('texts.new_invoice')) + ->withAttributes(['class'=>'primaryDropDown']) + ->withContents($actionLinks)->split() !!} + @endif @endif {!! Former::close() !!} diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 46bebbd68732..6fa6cb75a800 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -472,11 +472,13 @@ 'selected' => true, ]) @endif -
  • - @if (count(session(SESSION_USER_ACCOUNTS)) > 1) -
  • {!! link_to('/manage_companies', trans('texts.manage_companies')) !!}
  • - @elseif (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5) -
  • {!! link_to('/login?new_company=true', trans('texts.add_company')) !!}
  • +
  • + @if (Utils::isAdmin()) + @if (count(session(SESSION_USER_ACCOUNTS)) > 1) +
  • {!! link_to('/manage_companies', trans('texts.manage_companies')) !!}
  • + @elseif (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5) +
  • {!! link_to('/login?new_company=true', trans('texts.add_company')) !!}
  • + @endif @endif
  • {!! link_to('#', trans('texts.logout'), array('onclick'=>'logout()')) !!}
  • @@ -490,10 +492,14 @@ diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index 6e567bf3de75..ffe6c12bc1bc 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -38,7 +38,9 @@ {!! Button::normal(trans('texts.credits'))->asLinkTo(URL::to('/credits'))->appendIcon(Icon::create('list')) !!} @endif - {!! Button::primary(trans("texts.new_$entityType"))->asLinkTo(URL::to("/{$entityType}s/create"))->appendIcon(Icon::create('plus-sign')) !!} + @if (Auth::user()->hasPermission('create_all')) + {!! Button::primary(trans("texts.new_$entityType"))->asLinkTo(URL::to("/{$entityType}s/create"))->appendIcon(Icon::create('plus-sign')) !!} + @endif
    diff --git a/resources/views/user_account.blade.php b/resources/views/user_account.blade.php index 9ab41f845b7a..9be002ada05c 100644 --- a/resources/views/user_account.blade.php +++ b/resources/views/user_account.blade.php @@ -1,8 +1,12 @@
  • - @if (isset($user_id) && $user_id != Auth::user()->id) - - @else - + @if (Utils::isAdmin()) + @if (isset($user_id) && $user_id != Auth::user()->id) + + @else + + @endif + @else + @endif @if (file_exists($logo_path)) @@ -23,7 +27,6 @@ @if (isset($selected) && $selected) @endif -
  • \ No newline at end of file diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 71efd2fee6a8..3e77e9144283 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -23,7 +23,26 @@ {!! Former::text('first_name') !!} {!! Former::text('last_name') !!} {!! Former::text('email') !!} - + {!! Former::checkbox('is_admin') + ->label(' ') + ->text(trans('texts.administrator')) + ->help(trans('texts.administrator_help')) !!} + {!! Former::checkbox('permissions[create_all]') + ->value('create_all') + ->label(' ') + ->id('permissions_create_all') + ->text(trans('texts.user_create_all')) !!} + {!! Former::checkbox('permissions[view_all]') + ->value('view_all') + ->label(' ') + ->id('permissions_view_all') + ->text(trans('texts.user_view_all')) !!} + {!! Former::checkbox('permissions[edit_all]') + ->value('edit_all') + ->label(' ') + ->id('permissions_edit_all') + ->text(trans('texts.user_edit_all')) !!} +
    @@ -38,4 +57,14 @@ @section('onReady') $('#first_name').focus(); + $('#is_admin, #permissions_view_all').change(fixCheckboxes); + function fixCheckboxes(){ + var adminChecked = $('#is_admin').is(':checked'); + var viewChecked = $('#permissions_view_all').is(':checked'); + + $('#permissions_view_all').prop('disabled', adminChecked); + $('#permissions_create_all').prop('disabled', adminChecked); + $('#permissions_edit_all').prop('disabled', adminChecked || !viewChecked); + } + fixCheckboxes(); @stop \ No newline at end of file From 90e1f6695ce3398b83ed1635eaeb7b886d3bdb9a Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:07:11 -0400 Subject: [PATCH 2/8] Improve user permission support * Hide links based on permissions * Restrict editing/creating within other UI --- app/Http/Controllers/InvoiceController.php | 16 +++++++-- app/Http/Controllers/PaymentController.php | 10 ++++++ app/Http/Controllers/TaskController.php | 10 +++--- app/Http/Controllers/VendorController.php | 16 +++++++-- app/Models/EntityModel.php | 18 +++++++++- app/Models/Product.php | 1 + app/Ninja/Repositories/CreditRepository.php | 1 + app/Ninja/Repositories/ExpenseRepository.php | 4 +++ app/Ninja/Repositories/InvoiceRepository.php | 36 +++++++++++++------- app/Ninja/Repositories/PaymentRepository.php | 2 ++ app/Ninja/Repositories/TaskRepository.php | 2 ++ app/Services/BaseService.php | 8 +++-- app/Services/CreditService.php | 13 +++++++ app/Services/DatatableService.php | 4 ++- app/Services/ExpenseService.php | 14 +++++++- app/Services/InvoiceService.php | 28 ++++++++++++--- app/Services/PaymentService.php | 10 ++++++ app/Services/TaskService.php | 7 +++- app/Services/VendorService.php | 4 +++ resources/lang/en/texts.php | 2 +- resources/views/invoices/edit.blade.php | 9 +++-- 21 files changed, 182 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 089953f84c67..8c31cce798c8 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -345,10 +345,16 @@ class InvoiceController extends BaseController */ public function store(SaveInvoiceWithClientRequest $request) { + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + $action = Input::get('action'); $entityType = Input::get('entityType'); - $invoice = $this->invoiceService->save($request->input()); + $invoice = $this->invoiceService->save($data, true); $entityType = $invoice->getEntityType(); $message = trans("texts.created_{$entityType}"); @@ -379,10 +385,16 @@ class InvoiceController extends BaseController */ public function update(SaveInvoiceWithClientRequest $request) { + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + $action = Input::get('action'); $entityType = Input::get('entityType'); - $invoice = $this->invoiceService->save($request->input()); + $invoice = $this->invoiceService->save($data, true); $entityType = $invoice->getEntityType(); $message = trans("texts.updated_{$entityType}"); Session::flash('message', $message); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 7dc13ec6ed56..7e675d023f29 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -584,6 +584,11 @@ class PaymentController extends BaseController public function store(CreatePaymentRequest $request) { $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } + $input['invoice_id'] = Invoice::getPrivateId($input['invoice']); $input['client_id'] = Client::getPrivateId($input['client']); $payment = $this->paymentRepo->save($input); @@ -601,6 +606,11 @@ class PaymentController extends BaseController public function update(UpdatePaymentRequest $request) { $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } + $payment = $this->paymentRepo->save($input); Session::flash('message', trans('texts.updated_payment')); diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index ea3001dfc538..46178f09ddb2 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -68,10 +68,6 @@ class TaskController extends BaseController */ public function store() { - if(!$this->checkCreatePermission($response)){ - return $response; - } - return $this->save(); } @@ -187,6 +183,12 @@ class TaskController extends BaseController private function save($publicId = null) { $action = Input::get('action'); + + $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } if (in_array($action, ['archive', 'delete', 'restore'])) { return self::bulk(); diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index bab1479b446e..989246f824ca 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -77,7 +77,13 @@ class VendorController extends BaseController */ public function store(CreateVendorRequest $request) { - $vendor = $this->vendorService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $vendor = $this->vendorService->save($data); Session::flash('message', trans('texts.created_vendor')); @@ -195,7 +201,13 @@ class VendorController extends BaseController */ public function update(UpdateVendorRequest $request) { - $vendor = $this->vendorService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $vendor = $this->vendorService->save($data); Session::flash('message', trans('texts.updated_vendor')); diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 6006bcbd4071..aa6544e52f8b 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -134,8 +134,16 @@ class EntityModel extends Eloquent return static::whereId($item_id)->first()->user_id == Auth::user()->id; } + public static function canEditItemByOwner($user_id) { + if(Auth::user()->hasPermission('edit_all')) { + return true; + } + + return Auth::user()->id == $user_id; + } + public function canView() { - return static::canEdit($this); + return static::canViewItem($this); } public static function canViewItem($item) { @@ -149,4 +157,12 @@ class EntityModel extends Eloquent return static::whereId($item_id)->first()->user_id == Auth::user()->id; } + + public static function canViewItemByOwner($user_id) { + if(Auth::user()->hasPermission('view_all')) { + return true; + } + + return Auth::user()->id == $user_id; + } } diff --git a/app/Models/Product.php b/app/Models/Product.php index e00d0feebec8..4098c67063d2 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -1,5 +1,6 @@ make(); } - public function save($data) + public function save($data, $checkSubPermissions = false) { $account = \Auth::user()->account; $publicId = isset($data['public_id']) ? $data['public_id'] : false; @@ -406,29 +407,40 @@ class InvoiceRepository extends BaseRepository $task = false; if (isset($item['task_public_id']) && $item['task_public_id']) { $task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail(); - $task->invoice_id = $invoice->id; - $task->client_id = $invoice->client_id; - $task->save(); + if(!$checkSubPermissions || $task->canEdit()){ + $task->invoice_id = $invoice->id; + $task->client_id = $invoice->client_id; + $task->save(); + } } $expense = false; if (isset($item['expense_public_id']) && $item['expense_public_id']) { $expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail(); - $expense->invoice_id = $invoice->id; - $expense->client_id = $invoice->client_id; - $expense->save(); + if(!$checkSubPermissions || $expense->canEdit()){ + $expense->invoice_id = $invoice->id; + $expense->client_id = $invoice->client_id; + $expense->save(); + } } if ($productKey = trim($item['product_key'])) { if (\Auth::user()->account->update_products && ! strtotime($productKey)) { $product = Product::findProductByKey($productKey); if (!$product) { - $product = Product::createNew(); - $product->product_key = trim($item['product_key']); + if(!$checkSubPermissions || Product::canCreate()){ + $product = Product::createNew(); + $product->product_key = trim($item['product_key']); + } + else{ + $product = null; + } + } + if($product && (!$checkSubPermissions || $product->canEdit())){ + $product->notes = ($task || $expense) ? '' : $item['notes']; + $product->cost = $expense ? 0 : $item['cost']; + $product->save(); } - $product->notes = ($task || $expense) ? '' : $item['notes']; - $product->cost = $expense ? 0 : $item['cost']; - $product->save(); } } diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 838def01d338..a027cb62aedb 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -36,9 +36,11 @@ class PaymentRepository extends BaseRepository 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'invoices.invoice_number', 'contacts.first_name', 'contacts.last_name', diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 8f913c705a0d..a7655a8abd9e 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -27,6 +27,7 @@ class TaskRepository 'tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', @@ -36,6 +37,7 @@ class TaskRepository 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'tasks.is_running', 'tasks.time_log', 'tasks.created_at', diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php index ca1944d4e5ef..1e2f99c7dc34 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -5,6 +5,8 @@ use App\Services\DatatableService; class BaseService { + public static $bulk_actions = array('archive', 'restore', 'delete'); + use DispatchesJobs; protected function getRepo() @@ -14,14 +16,16 @@ class BaseService public function bulk($ids, $action) { - if ( ! $ids) { + if ( ! $ids || ! in_array($action, static::$bulk_actions) ) { return 0; } $entities = $this->getRepo()->findByPublicIdsWithTrashed($ids); foreach ($entities as $entity) { - $this->getRepo()->$action($entity); + if($entity->canEdit()){ + $this->getRepo()->$action($entity); + } } return count($entities); diff --git a/app/Services/CreditService.php b/app/Services/CreditService.php index 70c2d13048c2..2a130c551780 100644 --- a/app/Services/CreditService.php +++ b/app/Services/CreditService.php @@ -3,6 +3,8 @@ use Utils; use URL; use App\Services\BaseService; +use App\Models\Client; +use App\Models\Payment; use App\Ninja\Repositories\CreditRepository; @@ -30,6 +32,10 @@ class CreditService extends BaseService public function getDatatable($clientPublicId, $search) { $query = $this->creditRepo->find($clientPublicId, $search); + + if(!Utils::hasPermission('view_all')){ + $query->where('expenses.user_id', '=', Auth::user()->id); + } return $this->createDatatable(ENTITY_CREDIT, $query, !$clientPublicId); } @@ -40,6 +46,10 @@ class CreditService extends BaseService [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient @@ -78,6 +88,9 @@ class CreditService extends BaseService trans('texts.apply_credit'), function ($model) { return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1'; + }, + function ($model) { + return Payment::canCreate(); } ] ]; diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 456c619aef04..2f9af2856cf3 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -14,7 +14,9 @@ class DatatableService if ($actions && $showCheckbox) { $table->addColumn('checkbox', function ($model) { - return ''; }); } diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index e2150ac96829..2fc2afbc84b1 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -70,6 +70,10 @@ class ExpenseService extends BaseService function ($model) { if ($model->vendor_public_id) { + if(!Vendor::canViewItemByOwner($model->vendor_user_id)){ + return $model->vendor_name; + } + return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml(); } else { return ''; @@ -81,6 +85,10 @@ class ExpenseService extends BaseService function ($model) { if ($model->client_public_id) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); } else { return ''; @@ -90,6 +98,10 @@ class ExpenseService extends BaseService [ 'expense_date', function ($model) { + if(!Expense::canEditItemByOwner($model->user_id)){ + return Utils::fromSqlDate($model->expense_date); + } + return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml(); } ], @@ -169,7 +181,7 @@ class ExpenseService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_public_id && Invoice::canEditItemById($model->invoice_public_id); + return $model->invoice_public_id && Invoice::canEditItemByOwner($model->invoice_user_id); } ], [ diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index aea19ffc0803..c7c0be72acc0 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -9,6 +9,7 @@ use App\Ninja\Repositories\ClientRepository; use App\Events\QuoteInvitationWasApproved; use App\Models\Invitation; use App\Models\Invoice; +use App\Models\Client; use App\Models\Payment; class InvoiceService extends BaseService @@ -29,14 +30,26 @@ class InvoiceService extends BaseService return $this->invoiceRepo; } - public function save($data) + public function save($data, $checkSubPermissions = false) { if (isset($data['client'])) { - $client = $this->clientRepo->save($data['client']); - $data['client_id'] = $client->id; + $can_save_client = !$checkSubPermissions; + if(!$can_save_client){ + if(empty($data['client']['public_id']) || $data['client']['public_id']=='-1'){ + $can_save_client = Client::canCreate(); + } + else{ + $can_save_client = Client::wherePublicId($data['client']['public_id'])->first()->canEdit(); + } + } + + if($can_save_client){ + $client = $this->clientRepo->save($data['client']); + $data['client_id'] = $client->id; + } } - $invoice = $this->invoiceRepo->save($data); + $invoice = $this->invoiceRepo->save($data, $checkSubPermissions); $client = $invoice->client; $client->load('contacts'); @@ -124,12 +137,19 @@ class InvoiceService extends BaseService [ 'invoice_number', function ($model) use ($entityType) { + if(!Invoice::canEditItem($model)){ + return $model->invoice_number; + } + return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); } ], [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); }, ! $hideClient diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 10db86800628..3bf26d8ba206 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -11,6 +11,8 @@ use CreditCard; use App\Models\Payment; use App\Models\Account; use App\Models\Country; +use App\Models\Client; +use App\Models\Invoice; use App\Models\AccountGatewayToken; use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\AccountRepository; @@ -300,12 +302,20 @@ class PaymentService extends BaseService [ 'invoice_number', function ($model) { + if(!Invoice::canEditItemByOwner($model->invoice_user_id)){ + return $model->invoice_number; + } + return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); } ], [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index ae575e3b2a7b..70e7e22c7c88 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -5,6 +5,7 @@ use URL; use Utils; use App\Models\Task; use App\Models\Invoice; +use App\Models\Client; use App\Ninja\Repositories\TaskRepository; use App\Services\BaseService; @@ -48,6 +49,10 @@ class TaskService extends BaseService [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient @@ -97,7 +102,7 @@ class TaskService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_number && Invoice::canEditItemById($model->invoice_number); + return $model->invoice_number && Invoice::canEditItemByOwner($model->invoice_user_id); } ], [ diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index 44e27ce44f2a..6f9b4420d772 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -38,6 +38,10 @@ class VendorService extends BaseService public function getDatatable($search) { $query = $this->vendorRepo->find($search); + + if(!Utils::hasPermission('view_all')){ + $query->where('vendors.user_id', '=', Auth::user()->id); + } return $this->createDatatable(ENTITY_VENDOR, $query); } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 217841af7c05..ff129b37e0e1 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1068,7 +1068,7 @@ $LANG = array( 'administrator' => 'Administrator', 'administrator_help' => 'Allow user to manage users, change settings, and view and modify all data', 'user_create_all' => 'Create clients, invoices, etc.', - 'user_view_all' => 'View All clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', 'user_edit_all' => 'Edit all clients, invoices, etc.', ); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index ded91d7e0c90..e226d7a45b38 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -63,8 +63,13 @@

    - {{ trans('texts.edit_client') }} | - {!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!} + + @if($invoice->client->canView() || true) + @if ($invoice->client->canEdit() || true) + {{ trans('texts.edit_client') }} | + @endif + {!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!} + @endif
    From 9337d4b9a439b353608843ccca1e281f7deed9e5 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:29:30 -0400 Subject: [PATCH 3/8] Only show relevant data on Dashboard --- app/Http/Controllers/DashboardController.php | 42 +++++++++++++++----- resources/views/dashboard.blade.php | 20 ++++++++-- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 1e26fd4a715a..fb42733162a0 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -63,9 +63,15 @@ class DashboardController extends BaseController ->get(); $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) + ->where('activities.activity_type_id', '>', 0); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $activities = $activities->where('activities.user_id', '=', $user_id); + } + + $activities = $activities->orderBy('activities.created_at', 'desc') ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account') - ->where('activity_type_id', '>', 0) - ->orderBy('created_at', 'desc') ->take(50) ->get(); @@ -81,8 +87,14 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('invoices.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) - ->where('invoices.due_date', '<', date('Y-m-d')) - ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) + ->where('invoices.due_date', '<', date('Y-m-d')); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $pastDue = $pastDue->where('invoices.user_id', '=', $user_id); + } + + $pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->orderBy('invoices.due_date', 'asc') ->take(50) ->get(); @@ -100,9 +112,15 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '>=', date('Y-m-d')) - ->orderBy('invoices.due_date', 'asc') - ->take(50) - ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) + ->orderBy('invoices.due_date', 'asc'); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $upcoming = $upcoming->where('invoices.user_id', '=', $user_id); + } + + $upcoming = $upcoming->take(50) + ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->get(); $payments = DB::table('payments') @@ -114,8 +132,14 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('clients.is_deleted', '=', false) ->where('contacts.deleted_at', '=', null) - ->where('contacts.is_primary', '=', true) - ->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id']) + ->where('contacts.is_primary', '=', true); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $payments = $payments->where('payments.user_id', '=', $user_id); + } + + $payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id']) ->orderBy('payments.payment_date', 'desc') ->take(50) ->get(); diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index d36c8334cc2a..ad8b8c4e097b 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -116,7 +116,11 @@ @foreach ($payments as $payment) {!! \App\Models\Invoice::calcLink($payment) !!} - {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @if (\App\Models\Client::canViewItemByOwner($payment->client_user_id)) + {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @else + {{ trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email) }} + @endif {{ Utils::fromSqlDate($payment->payment_date) }} {{ Utils::formatMoney($payment->amount, $payment->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }} @@ -149,7 +153,11 @@ @if (!$invoice->is_quote) {!! \App\Models\Invoice::calcLink($invoice) !!} - {!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!} + @if (\App\Models\Client::canViewItemByOwner($payment->client_user_id)) + {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @else + {{ trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email) }} + @endif {{ Utils::fromSqlDate($invoice->due_date) }} {{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }} @@ -160,7 +168,7 @@
    -
    +

    @@ -180,7 +188,11 @@ @if (!$invoice->is_quote) {!! \App\Models\Invoice::calcLink($invoice) !!} - {!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!} + @if (\App\Models\Client::canViewItemByOwner($payment->client_user_id)) + {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @else + {{ trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email) }} + @endif {{ Utils::fromSqlDate($invoice->due_date) }} {{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }} From 36284431c9f94475d3b83aae01ac9ef471df519e Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:29:39 -0400 Subject: [PATCH 4/8] Remove testing code --- resources/views/invoices/edit.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index e226d7a45b38..6ab8228da649 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -64,8 +64,8 @@

    - @if($invoice->client->canView() || true) - @if ($invoice->client->canEdit() || true) + @if($invoice->client->canView()) + @if ($invoice->client->canEdit()) {{ trans('texts.edit_client') }} | @endif {!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!} From 9ef448c2b816826252672a86d0645c4c3bbc0ae9 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:38:35 -0400 Subject: [PATCH 5/8] Only show invoice button for expenses and tasks if an invoice can be created --- resources/views/list.blade.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index ffe6c12bc1bc..92f7d22cbd51 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -9,12 +9,14 @@ {!! Former::text('public_id') !!}
    - @if ($entityType == ENTITY_TASK) - {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} - @endif - @if ($entityType == ENTITY_EXPENSE) - {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} - @endif + @if (\App\Models\Invoice::canCreate()) + @if ($entityType == ENTITY_TASK) + {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} + @endif + @if ($entityType == ENTITY_EXPENSE) + {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} + @endif + @endif {!! DropdownButton::normal(trans('texts.archive'))->withContents([ ['label' => trans('texts.archive_'.$entityType), 'url' => 'javascript:submitForm("archive")'], From 67cc5599cc7bbe013237a402cd3dd874b2379986 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:38:42 -0400 Subject: [PATCH 6/8] Remove unneeded check --- app/Services/BaseService.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php index 1e2f99c7dc34..b68b06482a04 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -5,8 +5,6 @@ use App\Services\DatatableService; class BaseService { - public static $bulk_actions = array('archive', 'restore', 'delete'); - use DispatchesJobs; protected function getRepo() @@ -16,7 +14,7 @@ class BaseService public function bulk($ids, $action) { - if ( ! $ids || ! in_array($action, static::$bulk_actions) ) { + if ( ! $ids ) { return 0; } From 6ea4f168a26185bf3c804456e1af5bd6114d3cc6 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:43:30 -0400 Subject: [PATCH 7/8] Remove link to settings page for non-admins --- resources/views/header.blade.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 6fa6cb75a800..57f0409a9f01 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -488,19 +488,21 @@ From 2cd7228074e517c1e827506c46b05a3c8d1da2bf Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:45:11 -0400 Subject: [PATCH 8/8] Restrict access to tax rates and products --- app/Http/routes.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index 75e7ce9ab2ad..55f65e99a491 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -108,14 +108,6 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); - Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable')); - Route::resource('products', 'ProductController'); - Route::post('products/bulk', 'ProductController@bulk'); - - Route::get('api/tax_rates', array('as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable')); - Route::resource('tax_rates', 'TaxRateController'); - Route::post('tax_rates/bulk', 'TaxRateController@bulk'); - Route::resource('clients', 'ClientController'); Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); @@ -196,6 +188,14 @@ Route::group([ Route::resource('tokens', 'TokenController'); Route::post('tokens/bulk', 'TokenController@bulk'); + Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable')); + Route::resource('products', 'ProductController'); + Route::post('products/bulk', 'ProductController@bulk'); + + Route::get('api/tax_rates', array('as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable')); + Route::resource('tax_rates', 'TaxRateController'); + Route::post('tax_rates/bulk', 'TaxRateController@bulk'); + Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); Route::get('settings/data_visualizations', 'ReportController@d3'); Route::get('settings/charts_and_reports', 'ReportController@showReports');