From 0148d06205905995059671249bf8fa14c09a9702 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 19:08:00 -0400 Subject: [PATCH] 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 abefe0b5f7..755932691d 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 ee7f62686f..5124097636 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 163d75f622..7e1c01de9e 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 5a4b3011ad..26085c3d6b 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 2d4294d540..eb9fa0c437 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 fbbdc7c08e..089953f84c 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 24c4005f1d..7dc13ec6ed 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 882acb039e..7777c5a9d0 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 e09ed50b4b..ea3001dfc5 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 ee3f9d6554..0e5a8dd70d 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 5d25f29215..bab1479b44 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 72ffce9c8b..1142338b20 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 0000000000..af0e0015a3 --- /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 0c9aef7c1a..75e7ce9ab2 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 fcb210b0d7..cfcc97c001 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 b57eded2fb..6006bcbd40 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 6f03676618..e00d0feebe 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 7adf6f768b..15c39a7572 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 9f66f0d2af..1b2ae7815a 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 2e8ce31b5c..9df81663a5 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 1c33cb19e4..7252a7ee0e 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 cfd312622a..4d5f0d590a 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 f028acdf93..e577d93ac5 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 15f5007251..838def01d3 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 47a052378f..8f913c705a 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 5675a161a6..ca5878b0c1 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 81fc8fc7f4..df885f62e1 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 e5fcf4540f..3cd6910987 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 21242da54a..662fc8eab9 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 285efe717a..456c619aef 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 9f094240d0..e2150ac968 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 522c1a5dff..aea19ffc08 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 83429f5b20..10db868006 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 bd23d2a5e0..2786ccfc68 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 59f8fd95fe..ae575e3b2a 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 fcc0220036..8e31b4c30d 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 cd2dcf8d18..44e27ce44f 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 0000000000..2cdd599482 --- /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 e587699b92..217841af7c 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 57bfdb8eb5..c9426a8b3b 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 ee7ac115df..3166f9b800 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 46bebbd687..6fa6cb75a8 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 6e567bf3de..ffe6c12bc1 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 9ab41f845b..9be002ada0 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 71efd2fee6..3e77e91442 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