priorDisallowGrantChange = getenv('DISALLOW_GRANT_CHANGE'); putenv('DISALLOW_GRANT_CHANGE=false'); } protected function tearDown(): void { if ($this->priorDisallowGrantChange === false) { putenv('DISALLOW_GRANT_CHANGE'); } else { putenv('DISALLOW_GRANT_CHANGE=' . $this->priorDisallowGrantChange); } parent::tearDown(); } protected function createNonAdminEmployee(): int { $personData = [ 'first_name' => 'NonAdmin', 'last_name' => 'User', 'email' => 'nonadmin@test.com', 'phone_number' => '555-1234' ]; $employeeData = [ 'username' => 'nonadmin', 'password' => password_hash('password123', PASSWORD_DEFAULT), 'hash_version' => 2, 'language_code' => 'en', 'language' => 'english' ]; $grantsData = [ ['permission_id' => 'customers', 'menu_group' => 'home'], ['permission_id' => 'sales', 'menu_group' => 'home'] ]; $employeeModel = model(Employee::class); $employeeModel->save_employee($personData, $employeeData, $grantsData, NEW_ENTRY); return $employeeModel->get_found_rows(''); } protected function loginAsAdmin(): void { $session = Services::session(); $session->destroy(); $session->set('person_id', 1); $session->set('menu_group', 'office'); } protected function loginAsNonAdmin(int $personId): void { $session = Services::session(); $session->destroy(); $session->set('person_id', $personId); $session->set('menu_group', 'home'); } public function testNonAdminCannotViewAdminAccount(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsNonAdmin($nonAdminId); $response = $this->get('/employees/view/1'); $response->assertRedirect(); $this->assertStringContainsString('no_access', $response->getRedirectUrl()); } public function testNonAdminCannotModifyAdminAccount(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsNonAdmin($nonAdminId); $response = $this->post('/employees/save/1', [ 'first_name' => 'Hacked', 'last_name' => 'Admin', 'email' => 'hacked@evil.com', 'username' => 'admin' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success']); $this->assertStringContainsString('admin', strtolower($result['message'])); } public function testNonAdminCannotDeleteAdminAccount(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsNonAdmin($nonAdminId); $response = $this->post('/employees/delete', [ 'ids' => [1] ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success']); $this->assertStringContainsString('admin', strtolower($result['message'])); } public function testNonAdminCannotGrantPermissionsTheyDontHave(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsNonAdmin($nonAdminId); $targetEmployeeId = $nonAdminId + rand(1000, 9999); $this->createTestEmployee($targetEmployeeId); $response = $this->post('/employees/save/' . $targetEmployeeId, [ 'first_name' => 'Test', 'last_name' => 'Employee', 'email' => 'test@test.com', 'username' => 'testuser', 'grant_employees' => 'employees', 'grant_config' => 'config' ]); $employeeModel = model(Employee::class); $hasEmployeesGrant = $employeeModel->has_grant('employees', $targetEmployeeId); $hasConfigGrant = $employeeModel->has_grant('config', $targetEmployeeId); $this->assertFalse($hasEmployeesGrant); $this->assertFalse($hasConfigGrant); } public function testAdminCanModifyAnyAccount(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsAdmin(); $response = $this->post('/employees/save/' . $nonAdminId, [ 'first_name' => 'Modified', 'last_name' => 'User', 'email' => 'modified@test.com', 'username' => 'nonadmin' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); } public function testAdminCanDeleteAnyAccount(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsAdmin(); $response = $this->post('/employees/delete', [ 'ids' => [$nonAdminId] ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); } public function testUserCanModifyOwnAccount(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAsNonAdmin($nonAdminId); $response = $this->post('/employees/save/' . $nonAdminId, [ 'first_name' => 'Modified', 'last_name' => 'OwnAccount', 'email' => 'own@test.com', 'username' => 'nonadmin' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); } public function testPermissionDelegationRule(): void { $permissionsRequested = ['customers', 'employees', 'sales', 'config']; $userPermissions = ['customers', 'sales']; $isAdmin = false; $granted = []; foreach ($permissionsRequested as $perm) { if ($isAdmin || in_array($perm, $userPermissions)) { $granted[] = $perm; } } $this->assertEquals(['customers', 'sales'], $granted); } public function testAdminCanGrantAnyPermission(): void { $employeeId = $this->createNonAdminEmployee(); $this->loginAsAdmin(); putenv('DISALLOW_GRANT_CHANGE=false'); $permissionsRequested = ['customers', 'employees', 'sales', 'config']; $postData = [ 'first_name' => 'NonAdmin', 'last_name' => 'User', 'email' => 'nonadmin@test.com', 'username' => 'nonadmin' ]; foreach ($permissionsRequested as $perm) { $postData['grant_' . $perm] = $perm; } $response = $this->post('/employees/save/' . $employeeId, $postData); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); $employeeModel = model(Employee::class); foreach ($permissionsRequested as $perm) { $this->assertTrue($employeeModel->has_grant($perm, $employeeId)); } } public function testGrantChangeRequestFailsWhenGrantChangeDisallowed(): void { $employeeId = $this->createNonAdminEmployee(); $this->loginAsAdmin(); putenv('DISALLOW_GRANT_CHANGE=true'); $response = $this->post('/employees/save/' . $employeeId, [ 'first_name' => 'NonAdmin', 'last_name' => 'User', 'email' => 'nonadmin@test.com', 'username' => 'nonadmin', 'grant_employees' => 'employees' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success']); $employeeModel = model(Employee::class); $this->assertTrue($employeeModel->has_grant('customers', $employeeId)); $this->assertTrue($employeeModel->has_grant('sales', $employeeId)); $this->assertFalse($employeeModel->has_grant('employees', $employeeId)); } public function testNewEmployeeCreationWithGrantsFailsWhenGrantChangeDisallowed(): void { $this->loginAsAdmin(); putenv('DISALLOW_GRANT_CHANGE=true'); $response = $this->post('/employees/save', [ 'first_name' => 'Brand', 'last_name' => 'New', 'email' => 'brandnew@test.com', 'username' => 'brandnew', 'password' => 'password123', 'grant_customers' => 'customers' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success']); $createdEmployee = $this->db->table('employees')->where('username', 'brandnew')->get()->getRow(); $this->assertNull($createdEmployee); } public function testGrantChangeRequestSucceedsWhenGrantChangeAllowed(): void { $employeeId = $this->createNonAdminEmployee(); $this->loginAsAdmin(); putenv('DISALLOW_GRANT_CHANGE=false'); $response = $this->post('/employees/save/' . $employeeId, [ 'first_name' => 'NonAdmin', 'last_name' => 'User', 'email' => 'nonadmin@test.com', 'username' => 'nonadmin', 'grant_employees' => 'employees' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); $employeeModel = model(Employee::class); $this->assertTrue($employeeModel->has_grant('employees', $employeeId)); $this->assertFalse($employeeModel->has_grant('customers', $employeeId)); $this->assertFalse($employeeModel->has_grant('sales', $employeeId)); } public function testNewEmployeeCreationWithGrantsSucceedsWhenGrantChangeAllowed(): void { $this->loginAsAdmin(); putenv('DISALLOW_GRANT_CHANGE=false'); $response = $this->post('/employees/save', [ 'first_name' => 'Brand', 'last_name' => 'New2', 'email' => 'brandnew2@test.com', 'username' => 'brandnew2', 'password' => 'password123', 'grant_customers' => 'customers' ]); $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); $createdEmployee = $this->db->table('employees')->where('username', 'brandnew2')->get()->getRow(); $this->assertNotNull($createdEmployee); $employeeModel = model(Employee::class); $this->assertTrue($employeeModel->has_grant('customers', (int) $createdEmployee->person_id)); } }