diff --git a/composer.json b/composer.json index 9b1755f..c91da6c 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "fleetbase/core-api", - "version": "1.1.1-alpha", + "version": "1.1.2-alpha", "description": "Core Framework and Resources for Fleetbase API", "keywords": [ "fleetbase", diff --git a/migrations/2023_04_25_094306_create_groups_table.php b/migrations/2023_04_25_094306_create_groups_table.php index b06e328..7c16b93 100644 --- a/migrations/2023_04_25_094306_create_groups_table.php +++ b/migrations/2023_04_25_094306_create_groups_table.php @@ -19,6 +19,7 @@ public function up() $table->string('uuid', 191)->nullable()->unique(); $table->string('company_uuid', 191)->nullable()->index('groups_company_uuid_foreign'); $table->string('name')->nullable(); + $table->string('description', 500)->nullable(); $table->string('slug', 191)->nullable()->index(); $table->softDeletes(); $table->timestamp('created_at')->nullable()->index(); diff --git a/migrations/2023_04_25_094311_create_policies_table.php b/migrations/2023_04_25_094311_create_policies_table.php index 4e0dae7..6b30ee9 100644 --- a/migrations/2023_04_25_094311_create_policies_table.php +++ b/migrations/2023_04_25_094311_create_policies_table.php @@ -23,6 +23,22 @@ public function up() $table->timestamps(); $table->softDeletes(); }); + + Schema::create('model_has_policies', function (Blueprint $table) { + $table->uuid('policy_id')->index(); + + $table->string('model_type'); + $table->uuid('model_uuid'); + $table->index(['model_uuid', 'model_type'], 'model_has_policies_model_uuid_model_type_index'); + + $table + ->foreign('policy_id') + ->references('id') + ->on('policies') + ->onDelete('cascade'); + + $table->primary(['policy_id', 'model_uuid', 'model_type'], 'model_has_policies_policy_model_type_primary'); + }); } /** @@ -33,5 +49,6 @@ public function up() public function down() { Schema::dropIfExists('policies'); + Schema::dropIfExists('model_has_policies'); } }; diff --git a/migrations/2023_07_04_173018_make_roles_multi_tenant_table.php b/migrations/2023_07_04_173018_make_roles_multi_tenant_table.php new file mode 100644 index 0000000..bb68728 --- /dev/null +++ b/migrations/2023_07_04_173018_make_roles_multi_tenant_table.php @@ -0,0 +1,51 @@ +uuid('company_uuid')->nullable()->after('id')->index(); + $table->foreign('company_uuid')->references('uuid')->on('companies')->onDelete('cascade'); + $table->string('description')->nullable()->after('guard_name'); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::table($tableNames['roles'], function (Blueprint $table) { + $table->dropColumn('description'); + $table->dropForeign(['company_uuid']); + $table->dropColumn('company_uuid'); + $table->dropColumn('deleted_at'); + }); + } +}; diff --git a/seeds/FleetbaseSeeder.php b/seeds/FleetbaseSeeder.php new file mode 100644 index 0000000..d300315 --- /dev/null +++ b/seeds/FleetbaseSeeder.php @@ -0,0 +1,19 @@ +call(ExtensionSeeder::class); + $this->call(PermissionSeeder::class); + } +} diff --git a/seeds/PermissionSeeder.php b/seeds/PermissionSeeder.php new file mode 100644 index 0000000..89281f8 --- /dev/null +++ b/seeds/PermissionSeeder.php @@ -0,0 +1,228 @@ +name; + $resources = $schema->resources ?? []; + $permissions = $schema->permissions ?? null; + $guard = 'web'; + + // first create a wilcard permission for the entire schema + $administratorPolicy = Policy::firstOrCreate( + [ + 'name' => 'AdministratorAccess', + 'guard_name' => $guard, + 'description' => 'Provides full access to Fleetbase extensions and resources.', + ] + ); + + $permission = Permission::firstOrCreate( + [ + 'name' => $service . ' *', + 'guard_name' => $guard + ], + [ + 'name' => $service . ' *', + 'guard_name' => $guard + ] + ); + + // add wildcard permissions to administrator access policy + try { + $administratorPolicy->givePermissionTo($permission); + } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { + dd($e->getMessage(), $guard, $permission, $administratorPolicy); + } + + // output message for permissions creation + $this->output('Created (' . $guard . ') permission: ' . $permission->name); + + // check if schema has direct permissions to add + if (is_array($permissions)) { + foreach ($permissions as $action) { + $permission = Permission::firstOrCreate( + [ + 'name' => $service . ' ' . $action, + 'guard_name' => $guard + ], + [ + 'name' => $service . ' ' . $action, + 'guard_name' => $guard + ] + ); + + // add wildcard permissions to administrator access policy + try { + $administratorPolicy->givePermissionTo($permission); + } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { + dd($e->getMessage(), $guard, $permission, $administratorPolicy); + } + + // output message for permissions creation + $this->output('Created (' . $guard . ') permission: ' . $permission->name); + } + } + + // create a resource policy for full access + $fullAccessPolicy = Policy::firstOrCreate( + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . 'FullAccess', + 'guard_name' => $guard + ], + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . 'FullAccess', + 'description' => 'Provides full access to ' . Str::studly(data_get($schema, 'policyName')) . '.', + 'guard_name' => $guard + ] + ); + + // create a resource policy for read-only access + $readOnlyPolicy = Policy::firstOrCreate( + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . 'FullAccess', + 'guard_name' => $guard + ], + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . 'FullAccess', + 'description' => 'Provides read-only access to ' . Str::studly(data_get($schema, 'policyName')) . '.', + 'guard_name' => $guard + ] + ); + + // create wilcard permission for service and all resources + foreach ($resources as $resource) { + // create a resource policy for full access + $resourceFullAccessPolicy = Policy::firstOrCreate( + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . Str::studly(data_get($resource, 'name')) . 'FullAccess', + 'guard_name' => $guard + ], + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . Str::studly(data_get($resource, 'name')) . 'FullAccess', + 'description' => 'Provides full access to ' . Str::studly(data_get($schema, 'policyName')) . ' ' . Str::plural(data_get($resource, 'name')) . '.', + 'guard_name' => $guard + ] + ); + + // create a resource policy for read-only access + $resourceReadOnlyPolicy = Policy::firstOrCreate( + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . Str::studly(data_get($resource, 'name')) . 'FullAccess', + 'guard_name' => $guard + ], + [ + 'name' => Str::studly(data_get($schema, 'policyName')) . Str::studly(data_get($resource, 'name')) . 'FullAccess', + 'description' => 'Provides read-only access to ' . Str::studly(data_get($schema, 'policyName')) . ' ' . Str::plural(data_get($resource, 'name')) . '.', + 'guard_name' => $guard + ] + ); + + $permission = Permission::firstOrCreate( + [ + 'name' => $service . ' * ' . data_get($resource, 'name'), + 'guard_name' => $guard + ], + [ + 'name' => $service . ' * ' . data_get($resource, 'name'), + 'guard_name' => $guard + ] + ); + + // add wildcard permissions to full access policy + try { + $fullAccessPolicy->givePermissionTo($permission); + } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { + dd($e->getMessage(), $guard, $permission, $fullAccessPolicy); + } + try { + $resourceFullAccessPolicy->givePermissionTo($permission); + } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { + dd($e->getMessage(), $guard, $permission, $resourceFullAccessPolicy); + } + + // output message for permissions creation + $this->output('Created (' . $guard . ') permission: ' . $permission->name); + + // create action permissions + $resourceActions = array_merge($actions, data_get($resource, 'actions', [])); + + // if some actions should be excluded + if (is_array(data_get($resource, 'remove_actions', null))) { + foreach (data_get($resource, 'remove_actions') as $remove) { + if (($key = array_search($remove, $actions)) !== false) { + unset($actions[$key]); + } + } + } + + // create action permissions + foreach ($resourceActions as $action) { + $permission = Permission::firstOrCreate( + [ + 'name' => $service . ' ' . $action . ' ' . data_get($resource, 'name'), + 'guard_name' => $guard + ], + [ + 'name' => $service . ' ' . $action . ' ' . data_get($resource, 'name'), + 'guard_name' => $guard + ] + ); + + // add the permission to the read only policy + if ($action === 'view' || $action === 'list') { + try { + $readOnlyPolicy->givePermissionTo($permission); + } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { + dd($e->getMessage(), $guard, $permission, $readOnlyPolicy); + } + try { + $resourceReadOnlyPolicy->givePermissionTo($permission); + } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { + dd($e->getMessage(), $guard, $permission, $resourceReadOnlyPolicy); + } + } + + // output message for permissions creation + $this->output('Created (' . $guard . ') permission: ' . $permission->name); + } + } + } + + Schema::enableForeignKeyConstraints(); + } + + /** + * Simple echo to output to CLI + * + * @param string $line + * @return void + */ + public function output(string $line = ''): void + { + echo $line . PHP_EOL; + } +} diff --git a/seeds/RolesSeeder.php b/seeds/RolesSeeder.php new file mode 100644 index 0000000..a27d6bb --- /dev/null +++ b/seeds/RolesSeeder.php @@ -0,0 +1,18 @@ + 'api-key', + 'actions' => ['roll', 'export'] + ], + [ + 'name' => 'webhook', + 'actions' => [] + ], + [ + 'name' => 'socket', + 'actions' => [], + 'remove_actions' => ['create', 'update', 'delete'] + ], + [ + 'name' => 'log', + 'actions' => [], + 'remove_actions' => ['create', 'update', 'delete', 'export'] + ], + [ + 'name' => 'event', + 'actions' => [], + 'remove_actions' => ['create', 'update', 'delete'] + ] + ]; +} diff --git a/src/Auth/Schemas/IAM.php b/src/Auth/Schemas/IAM.php new file mode 100644 index 0000000..a425927 --- /dev/null +++ b/src/Auth/Schemas/IAM.php @@ -0,0 +1,58 @@ + 'group', + 'actions' => ['export'] + ], + [ + 'name' => 'user', + 'actions' => ['deactivate', 'export'] + ], + [ + 'name' => 'role', + 'actions' => ['export'] + ], + [ + 'name' => 'policy', + 'actions' => [] + ] + ]; +} diff --git a/src/Console/Commands/SeedDatabase.php b/src/Console/Commands/SeedDatabase.php index e62efbd..fa56c78 100644 --- a/src/Console/Commands/SeedDatabase.php +++ b/src/Console/Commands/SeedDatabase.php @@ -12,7 +12,7 @@ class SeedDatabase extends Command * * @var string */ - protected $signature = 'fleetbase:seed'; + protected $signature = 'fleetbase:seed {--class=FleetbaseSeeder}'; /** * The console command description. @@ -28,13 +28,24 @@ class SeedDatabase extends Command */ public function handle() { - Artisan::call( - 'db:seed', - [ - '--class' => 'Fleetbase\\Seeds\\ExtensionSeeder', - ] - ); + $class = $this->option('class'); - $this->info('Fleetbase seeds were run successfully.'); + if ($class) { + Artisan::call( + 'db:seed', + [ + '--class' => 'Fleetbase\\Seeds\\' . $class, + ] + ); + $this->info('Fleetbase ' . $class . ' Seeder was run Successfully!'); + } else { + Artisan::call( + 'db:seed', + [ + '--class' => 'Fleetbase\\Seeds\\FleetbaseSeeder', + ] + ); + $this->info('Fleetbase Seeders were run Successfully!'); + } } } diff --git a/src/Expansions/Request.php b/src/Expansions/Request.php index fc099ba..1f2488c 100644 --- a/src/Expansions/Request.php +++ b/src/Expansions/Request.php @@ -87,6 +87,23 @@ public function array() }; } + /** + * Check if param is array value. + * + * @return Closure + */ + public function isArray() + { + return function ($param) { + /** + * Context. + * + * @var \Illuminate\Support\Facades\Request $this + */ + return $this->has($param) && is_array($this->input($param)); + }; + } + /** * Retrieve input from the request as a integer. * diff --git a/src/Http/Controllers/Controller.php b/src/Http/Controllers/Controller.php index 7c7d8d9..96faa76 100644 --- a/src/Http/Controllers/Controller.php +++ b/src/Http/Controllers/Controller.php @@ -2,11 +2,9 @@ namespace Fleetbase\Http\Controllers; -use Fleetbase\Support\SocketClusterService; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; -use Illuminate\Http\Request; use Illuminate\Routing\Controller as BaseController; class Controller extends BaseController @@ -16,7 +14,7 @@ class Controller extends BaseController /** * Welcome message only */ - public function hello(Request $request) + public function hello() { return response()->json( [ @@ -37,4 +35,12 @@ public function time() ] ); } + + /** + * Use this route for arbitrary testing. + */ + public function test() + { + return response()->json(['status' => 'ok']); + } } diff --git a/src/Http/Controllers/Internal/v1/GroupController.php b/src/Http/Controllers/Internal/v1/GroupController.php index 5924ed7..32bbae5 100644 --- a/src/Http/Controllers/Internal/v1/GroupController.php +++ b/src/Http/Controllers/Internal/v1/GroupController.php @@ -5,6 +5,7 @@ use Fleetbase\Exports\GroupExport; use Fleetbase\Http\Controllers\FleetbaseController; use Fleetbase\Http\Requests\ExportRequest; +use Fleetbase\Exceptions\FleetbaseRequestValidationException; use Fleetbase\Models\GroupUser; use Fleetbase\Support\Utils; use Illuminate\Http\Request; @@ -18,7 +19,7 @@ class GroupController extends FleetbaseController * * @var string */ - public $resource = 'group'; + public $resource = 'group'; /** * Creates a record with request payload @@ -28,20 +29,65 @@ class GroupController extends FleetbaseController */ public function createRecord(Request $request) { - return $this->model::createRecordFromRequest($request, null, function (&$request, &$group) { - $users = $request->input('group.users'); - - foreach ($users as $user) { - GroupUser::firstOrCreate([ - 'group_uuid' => $group->uuid, - 'user_uuid' => Utils::get($user, 'uuid'), - ]); - } - - $group->load(['users']); - }); + try { + $record = $this->model->createRecordFromRequest($request, null, function (&$request, &$group) { + $users = $request->input('group.users'); + + foreach ($users as $id) { + GroupUser::firstOrCreate([ + 'group_uuid' => $group->uuid, + 'user_uuid' => $id, + ]); + } + + $group->load(['users']); + }); + + return ['group' => new $this->resource($record)]; + } catch (\Exception $e) { + return response()->error($e->getMessage()); + } catch (\Illuminate\Database\QueryException $e) { + return response()->error($e->getMessage()); + } catch (FleetbaseRequestValidationException $e) { + return response()->error($e->getErrors()); + } } + /** + * Updated a record with request payload + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function updateRecord(Request $request, string $id) + { + try { + $record = $this->model->updateRecordFromRequest($request, $id, function (&$request, &$group) { + $users = $request->input('group.users'); + + // users should always be an array of user ids + // we will first delete all group users where id is not in this array + GroupUser::whereNotIn('user_uuid', $users)->delete(); + + foreach ($users as $id) { + GroupUser::firstOrCreate([ + 'group_uuid' => $group->uuid, + 'user_uuid' => $id, + ]); + } + + $group->load(['users']); + }); + + return ['group' => new $this->resource($record)]; + } catch (\Exception $e) { + return response()->error($e->getMessage()); + } catch (\Illuminate\Database\QueryException $e) { + return response()->error($e->getMessage()); + } catch (FleetbaseRequestValidationException $e) { + return response()->error($e->getErrors()); + } + } /** * Export the groups to excel or csv diff --git a/src/Http/Controllers/Internal/v1/MetricController.php b/src/Http/Controllers/Internal/v1/MetricController.php new file mode 100644 index 0000000..d2a1245 --- /dev/null +++ b/src/Http/Controllers/Internal/v1/MetricController.php @@ -0,0 +1,72 @@ +whereNull('deleted_at')->whereHas('user')->count(); + // get number of groups + $metrics['groups_count'] = \Fleetbase\Models\Group::where('company_uuid', session('company'))->count(); + // get number of iams + $metrics['roles_count'] = \Fleetbase\Models\Role::where('company_uuid', session('company'))->count(); + // get number of roles + $metrics['policy_count'] = \Fleetbase\Models\Policy::where('company_uuid', session('company'))->count(); + + return response()->json($metrics); + } + + /** + * Dashboard configuration for IAM. + * + * @return \Illuminate\Http\Response + */ + public function iamDashboard() + { + $metrics = []; + // get number of users + $metrics['users_count'] = \Fleetbase\Models\CompanyUser::where('company_uuid', session('company'))->whereNull('deleted_at')->whereHas('user')->count(); + // get number of groups + $metrics['groups_count'] = \Fleetbase\Models\Group::where('company_uuid', session('company'))->count(); + // get number of iams + $metrics['roles_count'] = \Fleetbase\Models\Role::where('company_uuid', session('company'))->count(); + // get number of roles + $metrics['policy_count'] = \Fleetbase\Models\Policy::where('company_uuid', session('company'))->count(); + + // dashboard config + $dashboardConfig = [ + [ + 'size' => 12, + 'title' => 'Identity & Access Management Metrics', + 'classList' => [], + 'component' => null, + 'queryParams' => [], + 'widgets' => collect($metrics) + ->map(function ($value, $key) { + return [ + 'component' => 'count', + 'options' => [ + 'format' => null, + 'title' => str_replace('_', ' ', \Illuminate\Support\Str::title($key)), + 'value' => $value + ] + ]; + }) + ->values() + ->toArray() + ] + ]; + + return response()->json(array_values($dashboardConfig)); + } +} diff --git a/src/Http/Controllers/Internal/v1/PolicyController.php b/src/Http/Controllers/Internal/v1/PolicyController.php index c2f4807..41a1bfa 100644 --- a/src/Http/Controllers/Internal/v1/PolicyController.php +++ b/src/Http/Controllers/Internal/v1/PolicyController.php @@ -3,10 +3,10 @@ namespace Fleetbase\Http\Controllers\Internal\v1; use Fleetbase\Http\Controllers\FleetbaseController; +use Fleetbase\Exceptions\FleetbaseRequestValidationException; +use Fleetbase\Models\Permission; use Fleetbase\Models\Policy; -use Fleetbase\Support\Utils; use Illuminate\Http\Request; - class PolicyController extends FleetbaseController { /** @@ -14,7 +14,7 @@ class PolicyController extends FleetbaseController * * @var string */ - public $resource = 'policy'; + public $resource = 'policy'; /** * Creates a record by an identifier with request payload @@ -24,15 +24,22 @@ class PolicyController extends FleetbaseController */ public function createRecord(Request $request) { - return $this->model::createRecordFromRequest($request, null, function ($request, &$policy) { - if ($request->isArray('policy.permissions')) { - $permissions = collect($request->input('policy.permissions'))->map(function($permission) { - return Utils::get($permission, 'name'); - })->toArray(); + try { + $record = $this->model->createRecordFromRequest($request, null, function ($request, &$policy) { + if ($request->isArray('policy.permissions')) { + $permissions = Permission::whereIn('id', $request->array('policy.permissions'))->get(); + $policy->syncPermissions($permissions); + } + }); - $policy->syncPermissions($permissions); - } - }); + return ['policy' => new $this->resource($record)]; + } catch (\Exception $e) { + return response()->error($e->getMessage()); + } catch (\Illuminate\Database\QueryException $e) { + return response()->error($e->getMessage()); + } catch (FleetbaseRequestValidationException $e) { + return response()->error($e->getErrors()); + } } /** @@ -41,17 +48,24 @@ public function createRecord(Request $request) * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ - public function updateRecord(Request $request) + public function updateRecord(Request $request, string $id) { - return $this->model::updateRecordFromRequest($request, function ($request, &$policy) { - if ($request->isArray('policy.permissions')) { - $permissions = collect($request->input('policy.permissions'))->map(function($permission) { - return Utils::get($permission, 'name'); - })->toArray(); + try { + $record = $this->model->updateRecordFromRequest($request, $id, function ($request, &$policy) { + if ($request->isArray('policy.permissions')) { + $permissions = Permission::whereIn('id', $request->array('policy.permissions'))->get(); + $policy->syncPermissions($permissions); + } + }); - $policy->syncPermissions($permissions); - } - }); + return ['policy' => new $this->resource($record)]; + } catch (\Exception $e) { + return response()->error($e->getMessage()); + } catch (\Illuminate\Database\QueryException $e) { + return response()->error($e->getMessage()); + } catch (FleetbaseRequestValidationException $e) { + return response()->error($e->getErrors()); + } } /** @@ -60,7 +74,7 @@ public function updateRecord(Request $request) * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ - public function deleteRecord(Request $request) + public function deleteRecord($id, Request $request) { $id = $request->segment(4); $policy = Policy::find($id); diff --git a/src/Http/Controllers/Internal/v1/RoleController.php b/src/Http/Controllers/Internal/v1/RoleController.php index 5725394..f935b08 100644 --- a/src/Http/Controllers/Internal/v1/RoleController.php +++ b/src/Http/Controllers/Internal/v1/RoleController.php @@ -3,6 +3,8 @@ namespace Fleetbase\Http\Controllers\Internal\v1; use Fleetbase\Http\Controllers\FleetbaseController; +use Fleetbase\Exceptions\FleetbaseRequestValidationException; +use Fleetbase\Models\Permission; use Fleetbase\Support\Utils; use Illuminate\Http\Request; @@ -13,7 +15,7 @@ class RoleController extends FleetbaseController * * @var string */ - public $resource = 'role'; + public $resource = 'role'; /** * Creates a record by an identifier with request payload @@ -23,15 +25,22 @@ class RoleController extends FleetbaseController */ public function createRecord(Request $request) { - return $this->model::createRecordFromRequest($request, null, function ($request, &$role) { - if ($request->isArray('role.permissions')) { - $permissions = collect($request->input('role.permissions'))->map(function($permission) { - return Utils::get($permission, 'name'); - })->toArray(); + try { + $record = $this->model->createRecordFromRequest($request, null, function ($request, &$role) { + if ($request->isArray('role.permissions')) { + $permissions = Permission::whereIn('id', $request->array('role.permissions'))->get(); + $role->syncPermissions($permissions); + } + }); - $role->syncPermissions($permissions); - } - }); + return ['role' => new $this->resource($record)]; + } catch (\Exception $e) { + return response()->error($e->getMessage()); + } catch (\Illuminate\Database\QueryException $e) { + return response()->error($e->getMessage()); + } catch (FleetbaseRequestValidationException $e) { + return response()->error($e->getErrors()); + } } /** @@ -43,14 +52,21 @@ public function createRecord(Request $request) */ public function updateRecord(Request $request, string $id) { - return $this->model::updateRecordFromRequest($request, function ($request, &$role) { - if ($request->isArray('role.permissions')) { - $permissions = collect($request->input('role.permissions'))->map(function($permission) { - return Utils::get($permission, 'name'); - })->toArray(); + try { + $record = $this->model->updateRecordFromRequest($request, $id, function ($request, &$role) { + if ($request->isArray('role.permissions')) { + $permissions = Permission::whereIn('id', $request->array('role.permissions'))->get(); + $role->syncPermissions($permissions); + } + }); - $role->syncPermissions($permissions); - } - }); + return ['role' => new $this->resource($record)]; + } catch (\Exception $e) { + return response()->error($e->getMessage()); + } catch (\Illuminate\Database\QueryException $e) { + return response()->error($e->getMessage()); + } catch (FleetbaseRequestValidationException $e) { + return response()->error($e->getErrors()); + } } } diff --git a/src/Http/Controllers/Internal/v1/UserController.php b/src/Http/Controllers/Internal/v1/UserController.php index d2186da..856d027 100644 --- a/src/Http/Controllers/Internal/v1/UserController.php +++ b/src/Http/Controllers/Internal/v1/UserController.php @@ -41,8 +41,6 @@ public function current(Request $request) { $user = $request->user(); - // dd($user); - if (!$user) { return response()->error('No user session found', 401); } @@ -54,27 +52,6 @@ public function current(Request $request) ); } - // /** - // * Responds with the currently authenticated user. - // * - // * @param \Illuminate\Http\Request $request - // * @return \Illuminate\Http\Response - // */ - // public function findRecord(Request $request) - // { - // $user = $request->user(); - - // if (!$user) { - // return response()->error('No user session found', 401); - // } - - // return response()->json( - // [ - // 'user' => $user, - // ] - // ); - // } - /** * Creates a user, adds the user to company and sends an email to user about being added. * @@ -235,7 +212,6 @@ public function deactivate($id) } // $user->deactivate(); - // deactivate for company session $user->companies()->where('company_uuid', session('company'))->update(['status' => 'inactive']); $user = $user->refresh(); @@ -264,8 +240,9 @@ public function activate($id) return response()->error('No user found', 401); } - // $user->deactivate(); - // deactivate for company session + // $user->activate(); + // activate for company session + // maybe we dont want to activate for all organizations $user->companies()->where('company_uuid', session('company'))->update(['status' => 'active']); $user = $user->refresh(); diff --git a/src/Http/Filter/GroupFilter.php b/src/Http/Filter/GroupFilter.php new file mode 100644 index 0000000..9ae4e88 --- /dev/null +++ b/src/Http/Filter/GroupFilter.php @@ -0,0 +1,16 @@ +builder->where('company_uuid', $this->session->get('company')); + } + + public function query(?string $query) + { + $this->builder->search($query); + } +} diff --git a/src/Http/Filter/PermissionFilter.php b/src/Http/Filter/PermissionFilter.php new file mode 100644 index 0000000..fbac04a --- /dev/null +++ b/src/Http/Filter/PermissionFilter.php @@ -0,0 +1,11 @@ +builder->search($query); + } +} diff --git a/src/Http/Filter/PolicyFilter.php b/src/Http/Filter/PolicyFilter.php new file mode 100644 index 0000000..3a339db --- /dev/null +++ b/src/Http/Filter/PolicyFilter.php @@ -0,0 +1,20 @@ +builder->where( + function ($query) { + $query->where('company_uuid', $this->session->get('company'))->orWhereNull('company_uuid'); + } + ); + } + + public function query(?string $query) + { + $this->builder->search($query); + } +} diff --git a/src/Http/Filter/RoleFilter.php b/src/Http/Filter/RoleFilter.php new file mode 100644 index 0000000..cb12e30 --- /dev/null +++ b/src/Http/Filter/RoleFilter.php @@ -0,0 +1,16 @@ +builder->where('company_uuid', $this->session->get('company')); + } + + public function query(?string $query) + { + $this->builder->search($query); + } +} diff --git a/src/Http/Filter/UserFilter.php b/src/Http/Filter/UserFilter.php index b6e162b..c7c854b 100644 --- a/src/Http/Filter/UserFilter.php +++ b/src/Http/Filter/UserFilter.php @@ -19,4 +19,9 @@ function ($query) { } ); } + + public function query(?string $query) + { + $this->builder->search($query); + } } diff --git a/src/Http/Resources/FleetbaseResource.php b/src/Http/Resources/FleetbaseResource.php index f21e837..f0d0402 100644 --- a/src/Http/Resources/FleetbaseResource.php +++ b/src/Http/Resources/FleetbaseResource.php @@ -19,13 +19,13 @@ public function toArray($request) { $resource = parent::toArray($request); - if (Http::isInternalRequest()) { - // insert `uuid` after `id` if it doesn't exist already - if (!isset($resource['uuid'])) { - $resource['id'] = $this->id; - $resource = Arr::insertAfterKey($resource, ['uuid' => $this->uuid], 'id'); - } - } + // if (Http::isInternalRequest()) { + // // insert `uuid` after `id` if it doesn't exist already + // if (!isset($resource['uuid'])) { + // $resource['id'] = $this->id; + // $resource = Arr::insertAfterKey($resource, ['uuid' => $this->uuid], 'id'); + // } + // } return $resource; } diff --git a/src/Http/Resources/Organization.php b/src/Http/Resources/Organization.php index ae51cd0..742943f 100644 --- a/src/Http/Resources/Organization.php +++ b/src/Http/Resources/Organization.php @@ -4,7 +4,6 @@ use Fleetbase\Http\Resources\FleetbaseResource; use Fleetbase\Support\Http; -use Illuminate\Support\Arr; class Organization extends FleetbaseResource { diff --git a/src/Http/Resources/Policy.php b/src/Http/Resources/Policy.php new file mode 100644 index 0000000..9a7dea0 --- /dev/null +++ b/src/Http/Resources/Policy.php @@ -0,0 +1,53 @@ + $this->id, + 'company_uuid' => $this->company_uuid, + 'name' => $this->name, + 'guard_name' => $this->guard_name, + 'description' => $this->description, + 'type' => $this->type, + 'is_mutable' => $this->is_mutable, + 'is_deletable' => $this->is_deletable, + 'updated_at' => $this->updated_at, + 'created_at' => $this->created_at, + 'permissions' => $this->serializePermissions($this->permissions), + ]; + } + + /** + * Map permissins into the correct format with regard to pivot. + * + * @param \Illuminate\Support\Collection $permissions + * @return \Illuminate\Support\Collection + */ + public function serializePermissions($permissions): \Illuminate\Support\Collection + { + return $permissions->map( + function ($permission) { + return [ + 'id' => $permission->pivot->permission_id, + 'name' => $permission->name, + 'guard_name' => $permission->guard_name, + 'description' => $permission->description, + 'updated_at' => $permission->updated_at, + 'created_at' => $permission->created_at, + ]; + } + ); + } +} diff --git a/src/Http/Resources/Role.php b/src/Http/Resources/Role.php new file mode 100644 index 0000000..9c62b89 --- /dev/null +++ b/src/Http/Resources/Role.php @@ -0,0 +1,50 @@ + $this->id, + 'company_uuid' => $this->company_uuid, + 'name' => $this->name, + 'guard_name' => $this->guard_name, + 'description' => $this->description, + 'updated_at' => $this->updated_at, + 'created_at' => $this->created_at, + 'permissions' => $this->serializePermissions($this->permissions), + ]; + } + + /** + * Map permissins into the correct format with regard to pivot. + * + * @param \Illuminate\Support\Collection $permissions + * @return \Illuminate\Support\Collection + */ + public function serializePermissions($permissions): \Illuminate\Support\Collection + { + return $permissions->map( + function ($permission) { + return [ + 'id' => $permission->pivot->permission_id, + 'name' => $permission->name, + 'guard_name' => $permission->guard_name, + 'description' => $permission->description, + 'updated_at' => $permission->updated_at, + 'created_at' => $permission->created_at, + ]; + } + ); + } +} diff --git a/src/Models/CompanyUser.php b/src/Models/CompanyUser.php index c11512d..1af00ce 100644 --- a/src/Models/CompanyUser.php +++ b/src/Models/CompanyUser.php @@ -42,4 +42,14 @@ public function company() { return $this->belongsTo(Company::class); } + + /** + * Set the default status to `active` + * + * @return void + */ + public function setStatusAttribute($value = 'active') + { + $this->attributes['status'] = $value ?? 'active'; + } } diff --git a/src/Models/Group.php b/src/Models/Group.php index bcf634c..bd3fe2c 100644 --- a/src/Models/Group.php +++ b/src/Models/Group.php @@ -2,6 +2,7 @@ namespace Fleetbase\Models; +use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasPolicies; use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\HasUuid; @@ -12,7 +13,7 @@ class Group extends Model { - use HasUuid, HasApiModelBehavior, HasPermissions, HasPolicies, HasRoles, HasSlug; + use HasUuid, HasApiModelBehavior, HasPermissions, HasPolicies, HasRoles, HasSlug, Filterable; /** * The database connection to use. @@ -40,14 +41,14 @@ class Group extends Model * * @var array */ - protected $fillable = ['_key', 'company_uuid', 'name', 'slug']; + protected $fillable = ['_key', 'company_uuid', 'name', 'description', 'slug']; /** * The relationships that will always be appended. * * @var array */ - protected $with = ['users']; + protected $with = ['users', 'permissions', 'policies']; /** * Get the options for generating the slug. diff --git a/src/Models/Permission.php b/src/Models/Permission.php index e298f9e..85f64de 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -2,6 +2,7 @@ namespace Fleetbase\Models; +use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\Searchable; use Fleetbase\Traits\HasUuid; @@ -9,14 +10,21 @@ class Permission extends BasePermission { - use HasUuid, HasApiModelBehavior, Searchable; + use HasUuid, HasApiModelBehavior, Searchable, Filterable; /** - * The column to use for generating uuid. + * The database connection to use. * * @var string */ - public $uuidColumn = 'id'; + protected $connection = 'mysql'; + + /** + * The primary key for the model. + * + * @var string + */ + protected $primaryKey = 'id'; /** * The primary key type. @@ -25,6 +33,20 @@ class Permission extends BasePermission */ public $keyType = 'string'; + /** + * The column to use for generating uuid. + * + * @var string + */ + public $uuidColumn = 'id'; + + /** + * Indicates if the IDs are auto-incrementing. + * + * @var boolean + */ + public $incrementing = false; + /** * The attributes that can be queried * diff --git a/src/Models/Policy.php b/src/Models/Policy.php index e9dd4f2..843051d 100644 --- a/src/Models/Policy.php +++ b/src/Models/Policy.php @@ -2,14 +2,16 @@ namespace Fleetbase\Models; +use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\HasUuid; +use Fleetbase\Traits\Searchable; use Illuminate\Database\Eloquent\SoftDeletes; use Spatie\Permission\Traits\HasPermissions; class Policy extends Model { - use HasUuid, HasApiModelBehavior, HasPermissions, SoftDeletes; + use HasUuid, HasApiModelBehavior, HasPermissions, SoftDeletes, Searchable, Filterable; /** @__construct */ public function __construct(array $attributes = []) @@ -89,29 +91,53 @@ public function __construct(array $attributes = []) */ protected $appends = ['type', 'is_mutable', 'is_deletable']; + /** + * Cannot set permissions to Role model directly. + * + * @return void + */ + public function setPermissionsAttribute() + { + unset($this->attributes['permissions']); + } + + /** + * Check if the company_uuid attribute is set + * + * @return bool + */ public function getIsMutableAttribute() { return isset($this->company_uuid); } + /** + * Check if the company_uuid attribute is set + * + * @return bool + */ public function getIsDeletableAttribute() { return isset($this->company_uuid); } + /** + * Get the type of attribute based on the company_uuid + * + * @return string + */ public function getTypeAttribute() { return empty($this->company_uuid) ? 'FLB Managed' : 'Organization Managed'; } - public function attach($model) + /** + * Default guard should be `web`. + * + * @return void + */ + public function setGuardNameAttribute() { - if ($model instanceof Role) { - - } - - if ($model instanceof Group) { - - } + $this->attributes['guard_name'] = 'web'; } } diff --git a/src/Models/Role.php b/src/Models/Role.php index 4928836..693b550 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -2,6 +2,7 @@ namespace Fleetbase\Models; +use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasPolicies; use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\HasUuid; @@ -10,7 +11,7 @@ class Role extends BaseRole { - use HasUuid, HasApiModelBehavior, SoftDeletes, HasPolicies; + use HasUuid, HasApiModelBehavior, SoftDeletes, HasPolicies, Filterable; /** * The database connection to use. @@ -33,10 +34,77 @@ class Role extends BaseRole */ public $keyType = 'string'; + /** + * Indicates if the IDs are auto-incrementing. + * + * @var boolean + */ + public $incrementing = false; + + /** + * Indicates if the model should be timestamped. + * + * @var boolean + */ + public $timestamps = true; + /** * The column to use for generating uuid. * * @var string */ public $uuidColumn = 'id'; + + /** + * The attributes that can be queried + * + * @var array + */ + protected $searchableColumns = ['name']; + + /** + * The relationships that will always be appended. + * + * @var array + */ + protected $with = ['permissions']; + + /** + * Hotfix for tiemstamps bug. + * + * @return void + */ + public static function boot() + { + parent::boot(); + + static::creating(function ($model) { + $model->created_at = $model->freshTimestamp(); + $model->updated_at = $model->freshTimestamp(); + }); + + static::updating(function ($model) { + $model->updated_at = $model->freshTimestamp(); + }); + } + + /** + * Cannot set permissions to Role model directly. + * + * @return void + */ + public function setPermissionsAttribute() + { + unset($this->attributes['permissions']); + } + + /** + * Default guard should be `web`. + * + * @return void + */ + public function setGuardNameAttribute() + { + $this->attributes['guard_name'] = 'web'; + } } diff --git a/src/Models/User.php b/src/Models/User.php index e8d884f..f2e0a4a 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -24,6 +24,7 @@ use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasCacheableAttributes; use Fleetbase\Traits\HasMetaAttributes; +use Illuminate\Database\Eloquent\Concerns\HasTimestamps; use Laravel\Cashier\Billable; class User extends Authenticatable @@ -38,6 +39,7 @@ class User extends Authenticatable HasApiModelBehavior, HasCacheableAttributes, HasMetaAttributes, + HasTimestamps, LogsActivity, CausesActivity, SoftDeletes, @@ -66,6 +68,13 @@ class User extends Authenticatable */ public $incrementing = false; + /** + * Indicates if the model should be timestamped. + * + * @var boolean + */ + public $timestamps = true; + /** * The database table used by the model. * @@ -93,8 +102,8 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'uuid', // syncable - 'public_id', // syncable + 'uuid', + 'public_id', '_key', 'company_uuid', 'avatar_uuid', @@ -110,10 +119,11 @@ class User extends Authenticatable 'last_login', 'email_verified_at', 'phone_verified_at', + 'type', 'slug', 'status', - 'created_at', // syncable - 'updated_at', // syncable + 'created_at', + 'updated_at', ]; /** @@ -121,7 +131,7 @@ class User extends Authenticatable * * @var array */ - protected $guarded = ['type', 'password']; + protected $guarded = ['password']; /** * The attributes that should be hidden for arrays. @@ -137,6 +147,7 @@ class User extends Authenticatable */ protected $appends = [ 'avatar_url', + 'session_status', 'company_name', 'is_admin', 'types', @@ -367,6 +378,16 @@ public function setPasswordAttribute($value) $this->attributes['password'] = Hash::make($value); } + /** + * Set the default status to `active` + * + * @return void + */ + public function setStatusAttribute($value = 'active') + { + $this->attributes['status'] = $value ?? 'active'; + } + /** * Get the user timezone * @@ -404,7 +425,7 @@ public function changePassword($newPassword): User } /** - * Changes the users password + * Deactivate this user */ public function deactivate() { @@ -414,6 +435,17 @@ public function deactivate() return $this; } + /** + * Activate this user + */ + public function activate() + { + $this->status = 'active'; + $this->save(); + + return $this; + } + /** * Determines if the model is searchable. * diff --git a/src/Observers/UserObserver.php b/src/Observers/UserObserver.php index 220b09c..5f3b6eb 100644 --- a/src/Observers/UserObserver.php +++ b/src/Observers/UserObserver.php @@ -7,6 +7,20 @@ class UserObserver { + /** + * Handle the User "created" event. + * + * @param \Fleetbase\Models\User $user + * @return void + */ + public function created(User $user) + { + // create company user record + if (session('company')) { + CompanyUser::create(['company_uuid' => session('company'), 'user_uuid' => $user->uuid, 'status' => $user->status]); + } + } + /** * Handle the User "deleted" event. * diff --git a/src/Support/Utils.php b/src/Support/Utils.php index 0cb265b..a9441bb 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -1698,4 +1698,136 @@ public static function findPackageNamespace($path = null): ?string return $namespace; } + + /** + * Search installed composer packages for a specified keyword within their keywords array. + * The function reads the composer.lock file, which includes the exact versions of installed packages. + * If the keyword is found, the package's information is added to the result array. + * + * @param string $keyword The keyword to search for within the packages' keywords array. + * + * @throws Exception If the composer.lock file does not exist or if packages are not defined in it. + * + * @return array An associative array of packages that contain the keyword in their keywords array. + * The keys are the package names, and the values are the corresponding composer.json information. + */ + public static function findComposerPackagesWithKeyword($keyword = 'fleetbase-extension') + { + // Path to composer.lock file. + $filePath = './composer.lock'; + + // Check if file exists. + if (!file_exists($filePath)) { + throw new \Exception('composer.lock file does not exist'); + } + + // Read composer.lock content. + $fileContent = file_get_contents($filePath); + $composerData = json_decode($fileContent, true); + + // Check if packages are defined. + if (!isset($composerData['packages'])) { + throw new \Exception('Packages are not defined in the composer.lock file'); + } + + $foundPackages = []; + $packages = array_values($composerData['packages']); + + // Loop through packages. + foreach ($packages as $package) { + // Check if keywords array exists and contains the keyword. + if (isset($package['keywords']) && in_array($keyword, $package['keywords'])) { + // If package contains the keyword in its keywords array, add it to the result array. + $foundPackages[$package['name']] = $package; + } + } + + return $foundPackages; + } + + /** + * Get installed Fleetbase extensions. + * + * @return array + */ + public static function getInstalledFleetbaseExtensions() + { + return static::findComposerPackagesWithKeyword('fleetbase-extension'); + } + + /** + * Get the namespaced names of the authentication schemas found in the installed Fleetbase extensions. + * + * @return array + */ + public static function getAuthSchemaNamespaces() + { + $packages = static::getInstalledFleetbaseExtensions(); + $authSchemaClasses = []; + + // Local package directory + $localNamespace = 'Fleetbase\\'; + $localPackageSrcDirectory = base_path('vendor/fleetbase/core-api/src/'); + $localPackageDirectoryPath = $localPackageSrcDirectory . 'Auth/Schemas'; + + if (file_exists($localPackageDirectoryPath)) { + $localDirectoryIterator = new \DirectoryIterator($localPackageDirectoryPath); + + foreach ($localDirectoryIterator as $file) { + if ($file->isFile() && $file->getExtension() == 'php') { + $className = 'Auth\\Schemas\\' . $file->getBasename('.php'); + $authSchemaClasses[] = $localNamespace . $className; + } + } + } + + foreach ($packages as $packageName => $package) { + $srcDirectory = base_path('vendor/' . $packageName . '/src/'); + + if (!isset($package['autoload']['psr-4'])) { + continue; + } + + foreach ($package['autoload']['psr-4'] as $namespace => $directory) { + $directoryPath = $srcDirectory . 'Auth/Schemas'; + + // try path with namespace + if (!file_exists($directoryPath)) { + $directoryPath = $srcDirectory . str_replace('\\', '/', $namespace) . 'Auth/Schemas'; + } + + if (!file_exists($directoryPath)) { + continue; + } + + $directoryIterator = new \DirectoryIterator($directoryPath); + + foreach ($directoryIterator as $file) { + if ($file->isFile() && $file->getExtension() == 'php') { + $className = $namespace . 'Auth\\Schemas\\' . $file->getBasename('.php'); + $authSchemaClasses[] = $className; + } + } + } + } + + return array_values($authSchemaClasses); + } + + /** + * Get the authentication schemas instances from the installed Fleetbase extensions. + * + * @return array + */ + public static function getAuthSchemas() + { + $namespaces = static::getAuthSchemaNamespaces(); + + return array_map( + function ($schema) { + return app($schema); + }, + $namespaces + ); + } } diff --git a/src/Traits/HasApiControllerBehavior.php b/src/Traits/HasApiControllerBehavior.php index 8d01969..57b7a81 100644 --- a/src/Traits/HasApiControllerBehavior.php +++ b/src/Traits/HasApiControllerBehavior.php @@ -310,7 +310,6 @@ public function createRecord(Request $request) return new $this->resource($record); } catch (\Exception $e) { - dd($e); return response()->error($e->getMessage()); } catch (QueryException $e) { return response()->error($e->getMessage()); @@ -391,13 +390,14 @@ public function updateRecord(Request $request, string $id) * "message": "Resource not found" * } * - * @param int $id + * @param string $id * @return \Illuminate\Http\Response */ public function deleteRecord($id, Request $request) { if (Http::isInternalRequest($request)) { - $dataModel = $this->model->whereUuid($id)->first(); + $key = $this->model->getKeyName(); + $dataModel = $this->model->where($key, $id)->first(); } else { $dataModel = $this->model->wherePublicId($id)->first(); } diff --git a/src/Traits/HasApiModelBehavior.php b/src/Traits/HasApiModelBehavior.php index 26912f3..d37d108 100644 --- a/src/Traits/HasApiModelBehavior.php +++ b/src/Traits/HasApiModelBehavior.php @@ -175,7 +175,7 @@ public function createRecordFromRequest($request, ?callable $onBefore = null, ?c return $record; } - $builder = $this->where($this->getQualifiedKeyName(), $record->uuid); + $builder = $this->where($this->getQualifiedKeyName(), $record->getKey()); $builder = $this->withRelationships($request, $builder); $builder = $this->withCounts($request, $builder); @@ -222,7 +222,7 @@ public function updateRecordFromRequest(Request $request, $id, ?callable $onBefo $input = $this->fillSessionAttributes($input, [], ['updated_by_uuid']); if (is_callable($onBefore)) { - $before = $onBefore($request, $input); + $before = $onBefore($request, $record, $input); if ($before instanceof JsonResponse) { return $before; } diff --git a/src/Traits/HasPolicies.php b/src/Traits/HasPolicies.php index 2323e54..47c5367 100644 --- a/src/Traits/HasPolicies.php +++ b/src/Traits/HasPolicies.php @@ -41,11 +41,11 @@ public function getPolicyClass() public function policies(): BelongsToMany { return $this->morphToMany( - config('permission.models.role'), + \Fleetbase\Models\Policy::class, 'model', - config('permission.table_names.model_has_policies'), - config('permission.column_names.model_morph_key'), - 'role_id' + 'model_has_policies', + 'model_uuid', + 'policy_id' ); } diff --git a/src/routes.php b/src/routes.php index cfe5892..826a862 100644 --- a/src/routes.php +++ b/src/routes.php @@ -13,6 +13,10 @@ | */ +if (env('APP_DEBUG') === true) { + Route::get('test', 'Fleetbase\Http\Controllers\Controller@test'); +} + Route::prefix(config('fleetbase.api.routing.prefix', '/'))->namespace('Fleetbase\Http\Controllers')->group( function ($router) { $router->get('/', 'Controller@hello'); @@ -59,6 +63,12 @@ function ($router) { $router->get('font-awesome-icons', 'LookupController@fontAwesomeIcons'); } ); + $router->group( + ['prefix' => 'users'], + function ($router) { + $router->post('accept-company-invite', 'UserController@acceptCompanyInvite'); + } + ); $router->group( ['prefix' => 'settings'], function ($router) { @@ -76,6 +86,13 @@ function ($router, $controller) { $router->get('export', $controller('export')); } ); + $router->fleetbaseRoutes( + 'metrics', + function ($router, $controller) { + $router->get('iam', $controller('iam')); + $router->get('iam-dashboard', $controller('iamDashboard')); + } + ); $router->fleetbaseRoutes( 'settings', function ($router, $controller) { @@ -108,6 +125,12 @@ function ($router, $controller) { 'users', function ($router, $controller) { $router->get('me', $controller('current')); + $router->get('export', $controller('export')); + $router->patch('deactivate/{id}', $controller('deactivate')); + $router->patch('activate/{id}', $controller('activate')); + $router->delete('remove-from-company/{id}', $controller('removeFromCompany')); + $router->post('resend-invite', $controller('resendInvitation')); + $router->post('set-password', $controller('setCurrentUserPassword')); } ); $router->fleetbaseRoutes('user-devices');