From f650f1718148bc4a64fcaae69e619ee8cddd56bf Mon Sep 17 00:00:00 2001 From: objec Date: Tue, 19 May 2026 16:16:27 +0400 Subject: [PATCH] Push missing language strings file changes and HomeTest Signed-off-by: objec --- .../Language/ta/MailchimpPlugin.php | 1 - .../Language/th/MailchimpPlugin.php | 1 - .../Language/tl/MailchimpPlugin.php | 1 - .../Language/tr/MailchimpPlugin.php | 1 - .../Language/uk/MailchimpPlugin.php | 1 - .../Language/ur/MailchimpPlugin.php | 1 - .../Language/vi/MailchimpPlugin.php | 1 - .../Language/zh-Hans/MailchimpPlugin.php | 1 - .../Language/zh-Hant/MailchimpPlugin.php | 1 - tests/Controllers/HomeTest.php | 194 +++++++++--------- 10 files changed, 97 insertions(+), 106 deletions(-) diff --git a/app/Plugins/MailchimpPlugin/Language/ta/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/ta/MailchimpPlugin.php index 7942698e3..43e4c1d52 100644 --- a/app/Plugins/MailchimpPlugin/Language/ta/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/ta/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'மெயில்சிம்ப் நிலை', 'description' => 'வாடிக்கையாளர்கள் உருவாக்கப்படும்போது அல்லது புதுப்பிக்கப்படும்போது அவர்களை மின்னஞ்சல் பட்டியல்களுடன் ஒத்திசைக்க மெயில்சிம்புடன் ஒருங்கிணைக்கவும்.', 'email_client' => 'மின்னஞ்சல் வாடிக்கையாளர்', - 'info' => 'மெயில்சிம்ப்', 'key_successfully' => 'API சாவி சரியானது.', 'key_unsuccessfully' => 'API சாவி தவறானது.', diff --git a/app/Plugins/MailchimpPlugin/Language/th/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/th/MailchimpPlugin.php index 5e90b0bc0..65293c224 100644 --- a/app/Plugins/MailchimpPlugin/Language/th/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/th/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'สถานะของระบบส่งเมล์เมล์ชิม', 'description' => 'เชื่อมต่อกับ Mailchimp เพื่อซิงโครไนซ์ลูกค้าเข้ากับรายการเมล์เมื่อมีการสร้างหรืออัปเดต', 'email_client' => 'โปรแกรมรับส่งเมล', - 'info' => 'ระบบส่งอีเมล์ชิม', 'key_successfully' => 'คีย์ API ถูกต้อง', 'key_unsuccessfully' => 'คีย์ API ไม่ถูกต้อง', diff --git a/app/Plugins/MailchimpPlugin/Language/tl/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/tl/MailchimpPlugin.php index 53a7dd1b6..0a72aabb8 100644 --- a/app/Plugins/MailchimpPlugin/Language/tl/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/tl/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'Mailchimp status', 'description' => 'I-ugnay ang Mailchimp upang i-sync ang mga customer sa mga mailing list kapag sila ay ginawa o na-update.', 'email_client' => 'Email client', - 'info' => 'Mailchimp', 'key_successfully' => 'Ang API key ay valid.', 'key_unsuccessfully' => 'Ang API key ay invalid.', diff --git a/app/Plugins/MailchimpPlugin/Language/tr/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/tr/MailchimpPlugin.php index 7e45e33b7..cf34581a2 100644 --- a/app/Plugins/MailchimpPlugin/Language/tr/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/tr/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'MailChimp durumu', 'description' => 'Mailchimp ile entegre olarak, müşteriler oluşturulduğunda veya güncellendiğinde onları posta listeleriyle senkronize edin.', 'email_client' => 'E-posta istemcisi', - 'info' => 'MailChimp', 'key_successfully' => 'API anahtarı geçerli.', 'key_unsuccessfully' => 'API anahtarı geçersiz.', diff --git a/app/Plugins/MailchimpPlugin/Language/uk/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/uk/MailchimpPlugin.php index 13eb1f7e4..4742fc3dc 100644 --- a/app/Plugins/MailchimpPlugin/Language/uk/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/uk/MailchimpPlugin.php @@ -13,7 +13,6 @@ return [ 'description' => 'Інтегруйте Mailchimp для синхронізації клієнтів зі списками розсилки при їх створенні або оновленні.', 'email_client' => 'Поштовий клієнт', - 'info' => 'MailChimp', 'key_successfully' => 'API-ключ дійсний.', 'key_unsuccessfully' => 'API-ключ недійсний.', diff --git a/app/Plugins/MailchimpPlugin/Language/ur/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/ur/MailchimpPlugin.php index 98de8ba31..51320fac5 100644 --- a/app/Plugins/MailchimpPlugin/Language/ur/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/ur/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'میل چیمپ گاہک کی صورتحال', 'description' => 'جب گاہک بنائے جائیں یا اپ ڈیٹ کیے جائیں تو انہیں میلنگ لسٹوں کے ساتھ ہم آہنگ کرنے کے لیے میل چیمپ کے ساتھ انٹیگریٹ کریں۔', 'email_client' => 'ای میل کلائنٹ', - 'info' => 'میل چیمپ', 'key_successfully' => 'API کلید درست ہے۔', 'key_unsuccessfully' => 'API کلید غلط ہے۔', diff --git a/app/Plugins/MailchimpPlugin/Language/vi/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/vi/MailchimpPlugin.php index 322373cff..8187a6339 100644 --- a/app/Plugins/MailchimpPlugin/Language/vi/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/vi/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'Trạng thái tài khoản Mailchimp', 'description' => 'Tích hợp với Mailchimp để đồng bộ hóa khách hàng với danh sách gửi thư khi họ được tạo hoặc cập nhật.', 'email_client' => 'Trình khách nhận thư', - 'info' => 'Mailchimp', 'key_successfully' => 'Khóa API hợp lệ.', 'key_unsuccessfully' => 'Khóa API không hợp lệ.', diff --git a/app/Plugins/MailchimpPlugin/Language/zh-Hans/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/zh-Hans/MailchimpPlugin.php index 4c69ff14e..02097a09c 100644 --- a/app/Plugins/MailchimpPlugin/Language/zh-Hans/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/zh-Hans/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'Mailchimp 客户状态', 'description' => '与 Mailchimp 集成,在创建或更新客户时将客户同步到邮件列表。', 'email_client' => '邮件客户端', - 'info' => 'Mailchimp', 'key_successfully' => 'API 密钥有效。', 'key_unsuccessfully' => 'API 密钥无效。', diff --git a/app/Plugins/MailchimpPlugin/Language/zh-Hant/MailchimpPlugin.php b/app/Plugins/MailchimpPlugin/Language/zh-Hant/MailchimpPlugin.php index f9fcfc595..3fccab6a7 100644 --- a/app/Plugins/MailchimpPlugin/Language/zh-Hant/MailchimpPlugin.php +++ b/app/Plugins/MailchimpPlugin/Language/zh-Hant/MailchimpPlugin.php @@ -12,7 +12,6 @@ return [ 'customer_status' => 'MailChimp 電子報行銷狀態', 'description' => '與 Mailchimp 集成,在創建或更新客戶時將客戶同步到郵件列表。', 'email_client' => '電郵客戶端', - 'info' => '邮件猩猩', 'key_successfully' => 'API 密鑰有效。', 'key_unsuccessfully' => 'API 密鑰無效。', diff --git a/tests/Controllers/HomeTest.php b/tests/Controllers/HomeTest.php index 5d5f70653..c5f3a9d56 100644 --- a/tests/Controllers/HomeTest.php +++ b/tests/Controllers/HomeTest.php @@ -10,7 +10,7 @@ use App\Models\Employee; /** * Test suite for Home controller password validation - * + * * Tests the critical fix for password minimum length validation bypass * Issue: Code was checking hashed password length (always 60 chars) instead of actual password * Fix: Validate raw password length BEFORE hashing @@ -35,13 +35,13 @@ class HomeTest extends CIUnitTestCase /** * Test password validation rejects passwords shorter than 8 characters - * + * * @return void */ public function testPasswordMinLength_Rejects7Characters(): void { $this->resetSession(); - + // Attempt to change password to 7 characters $response = $this->post('/home/save', [ 'employee_id' => 1, @@ -49,29 +49,29 @@ class HomeTest extends CIUnitTestCase 'current_password' => 'pointofsale', 'password' => '1234567' // 7 characters ]); - + // Assert failure response $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success'], 'Password with 7 chars should be rejected'); $this->assertEquals(-1, $result['id']); - + // Verify password was not changed $employee = model(Employee::class); - $admin = $employee->get_info(1); - $this->assertTrue(password_verify('pointofsale', $admin->password), + $admin = $employee->getInfo(1); + $this->assertTrue(password_verify('pointofsale', $admin->password), 'Password should not have been changed'); } - + /** * Test password validation accepts passwords with exactly 8 characters - * + * * @return void */ public function testPasswordMinLength_Accepts8Characters(): void { $this->resetSession(); - + // Change password to exactly 8 characters $response = $this->post('/home/save', [ 'employee_id' => 1, @@ -79,19 +79,19 @@ class HomeTest extends CIUnitTestCase 'current_password' => 'pointofsale', 'password' => 'pa$$w0rd' // Exactly 8 characters including special chars ]); - + // Assert success response $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success'], 'Password with 8 chars should be accepted'); $this->assertEquals(1, $result['id']); - + // Verify password was changed $employee = model(Employee::class); - $admin = $employee->get_info(1); - $this->assertTrue(password_verify('pa$$w0rd', $admin->password), + $admin = $employee->getInfo(1); + $this->assertTrue(password_verify('pa$$w0rd', $admin->password), 'Password with 8 chars should be accepted'); - + // Restore original password $employee->change_password([ 'username' => 'admin', @@ -99,16 +99,16 @@ class HomeTest extends CIUnitTestCase 'hash_version' => 2 ], 1); } - + /** * Test password validation rejects empty password - * + * * @return void */ public function testPasswordMinLength_RejectsEmptyString(): void { $this->resetSession(); - + // Attempt to set empty password $response = $this->post('/home/save', [ 'employee_id' => 1, @@ -116,22 +116,22 @@ class HomeTest extends CIUnitTestCase 'current_password' => 'pointofsale', 'password' => '' // Empty string ]); - + $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success'], 'Empty password should be rejected'); $this->assertEquals(-1, $result['id']); } - + /** * Test password validation rejects whitespace-only passwords - * + * * @return void */ public function testPasswordMinLength_RejectsWhitespaceOnly(): void { $this->resetSession(); - + // Attempt to set password as only whitespace $response = $this->post('/home/save', [ 'employee_id' => 1, @@ -139,42 +139,42 @@ class HomeTest extends CIUnitTestCase 'current_password' => 'pointofsale', 'password' => ' ' // 8 spaces but empty actual password ]); - + $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success'], 'Whitespace only password should be rejected'); $this->assertEquals(-1, $result['id']); } - + /** * Test password validation accepts passwords with special characters * as long as they meet minimum length - * + * * @return void */ public function testPasswordMinLength_AcceptsSpecialCharacters(): void { $this->resetSession(); - + $specialPassword = 'Str0ng!@#$'; // 11 characters with special chars - + $response = $this->post('/home/save', [ 'employee_id' => 1, 'username' => 'admin', 'current_password' => 'pointofsale', 'password' => $specialPassword ]); - + $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success'], 'Password with special chars should be accepted'); $this->assertEquals(1, $result['id']); - + // Verify password works $employee = model(Employee::class); - $admin = $employee->get_info(1); + $admin = $employee->getInfo(1); $this->assertTrue(password_verify($specialPassword, $admin->password)); - + // Restore original password $employee->change_password([ 'username' => 'admin', @@ -182,20 +182,20 @@ class HomeTest extends CIUnitTestCase 'hash_version' => 2 ], 1); } - + /** * Regression test: Verify previous vulnerable behavior is fixed - * + * * Before fix: 1-character passwords like "a" were accepted because * code checked len(hashed_password) which is always 60 for bcrypt * After fix: Raw password is validated before hashing - * + * * @return void */ public function testPasswordMinLength_RejectsPreviousBehavior(): void { $this->resetSession(); - + // Attempt the previously vulnerable case: single character password $response = $this->post('/home/save', [ 'employee_id' => 1, @@ -203,23 +203,23 @@ class HomeTest extends CIUnitTestCase 'current_password' => 'pointofsale', 'password' => 'a' // Previously allowed due to bug ]); - + // This should now fail $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success'], 'Single character password should be rejected (CVE fix)'); $this->assertEquals(-1, $result['id']); - + // Verify password was NOT changed $employee = model(Employee::class); - $admin = $employee->get_info(1); - $this->assertTrue(password_verify('pointofsale', $admin->password), + $admin = $employee->getInfo(1); + $this->assertTrue(password_verify('pointofsale', $admin->password), 'Single character password should be rejected (CVE fix)'); } - + /** * Helper method to reset session - * + * * @return void */ protected function resetSession(): void @@ -228,10 +228,10 @@ class HomeTest extends CIUnitTestCase $session->destroy(); $session->set('person_id', 1); // Admin user } - + /** * Create a non-admin employee for testing - * + * * @param array $overrides Optional overrides for username, email, password, etc. * @return int The person_id of the created employee */ @@ -243,7 +243,7 @@ class HomeTest extends CIUnitTestCase 'email' => $overrides['email'] ?? 'nonadmin@test.com', 'phone_number' => $overrides['phone_number'] ?? '555-1234' ]; - + $employeeData = [ 'username' => $overrides['username'] ?? 'nonadmin', 'password' => password_hash($overrides['password'] ?? 'password123', PASSWORD_DEFAULT), @@ -251,21 +251,21 @@ class HomeTest extends CIUnitTestCase '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(''); } - + /** * Login as a specific user - * + * * @param int $personId * @return void */ @@ -276,150 +276,150 @@ class HomeTest extends CIUnitTestCase $session->set('person_id', $personId); $session->set('menu_group', 'home'); } - + // ========== BOLA Authorization Tests ========== - + /** * Test non-admin cannot view admin password change form * BOLA vulnerability fix: GHSA-q58g-gg7v-f9rf - * + * * @return void */ public function testNonAdminCannotViewAdminPasswordForm(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId); - + $response = $this->get('/home/changePassword/1'); - + $response->assertStatus(403); } - + /** * Test non-admin cannot change admin password * BOLA vulnerability fix: GHSA-q58g-gg7v-f9rf - * + * * @return void */ public function testNonAdminCannotChangeAdminPassword(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId); - + $response = $this->post('/home/save/1', [ 'username' => 'admin', 'current_password' => 'pointofsale', 'password' => 'hacked123' ]); - + $response->assertStatus(403); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success']); - + // Verify admin password was NOT changed $employee = model(Employee::class); - $admin = $employee->get_info(1); - $this->assertTrue(password_verify('pointofsale', $admin->password), + $admin = $employee->getInfo(1); + $this->assertTrue(password_verify('pointofsale', $admin->password), 'Admin password should not have been changed by non-admin'); } - + /** * Test user can view their own password change form - * + * * @return void */ public function testUserCanViewOwnPasswordForm(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId); - + $response = $this->get('/home/changePassword/' . $nonAdminId); - + $response->assertStatus(200); $response->assertSee('nonadmin'); // Username should be visible } - + /** * Test user can change their own password - * + * * @return void */ public function testUserCanChangeOwnPassword(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId); - + $response = $this->post('/home/save/' . $nonAdminId, [ 'username' => 'nonadmin', 'current_password' => 'password123', 'password' => 'newpassword123' ]); - + $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); - + // Verify password was changed $employee = model(Employee::class); - $user = $employee->get_info($nonAdminId); + $user = $employee->getInfo($nonAdminId); $this->assertTrue(password_verify('newpassword123', $user->password)); } - + /** * Test admin can view any user's password form - * + * * @return void */ public function testAdminCanViewAnyPasswordForm(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->resetSession(); // Login as admin - + $response = $this->get('/home/changePassword/' . $nonAdminId); - + $response->assertStatus(200); $response->assertSee('nonadmin'); } - + /** * Test admin can change any user's password - * + * * @return void */ public function testAdminCanChangeAnyPassword(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->resetSession(); // Login as admin - + $response = $this->post('/home/save/' . $nonAdminId, [ 'username' => 'nonadmin', 'current_password' => 'password123', 'password' => 'adminset123' ]); - + $response->assertStatus(200); $result = json_decode($response->getJSON(), true); $this->assertTrue($result['success']); - + // Verify password was changed $employee = model(Employee::class); - $user = $employee->get_info($nonAdminId); + $user = $employee->getInfo($nonAdminId); $this->assertTrue(password_verify('adminset123', $user->password)); } - + /** * Test default employee_id parameter uses current user - * + * * @return void */ public function testDefaultEmployeeIdUsesCurrentUser(): void { $nonAdminId = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId); - + // Calling without employee_id should use current user $response = $this->get('/home/changePassword'); - + $response->assertStatus(200); $response->assertSee('nonadmin'); } @@ -427,56 +427,56 @@ class HomeTest extends CIUnitTestCase /** * Test non-admin cannot view another non-admin's password form * IDOR vulnerability fix: GHSA-mcc2-8rp2-q6ch - * + * * @return void */ public function testNonAdminCannotViewOtherNonAdminPasswordForm(): void { $nonAdminId1 = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId1); - + $otherUserId = $this->createNonAdminEmployee([ 'username' => 'otheruser', 'email' => 'other@test.com', 'password' => 'password456' ]); - + $response = $this->get('/home/changePassword/' . $otherUserId); - + $response->assertStatus(403); } /** * Test non-admin cannot change another non-admin's password * IDOR vulnerability fix: GHSA-mcc2-8rp2-q6ch - * + * * @return void */ public function testNonAdminCannotChangeOtherNonAdminPassword(): void { $nonAdminId1 = $this->createNonAdminEmployee(); $this->loginAs($nonAdminId1); - + $victimId = $this->createNonAdminEmployee([ 'username' => 'victimuser', 'email' => 'victim@test.com', 'password' => 'victimpass123' ]); - + $response = $this->post('/home/save/' . $victimId, [ 'username' => 'victimuser', 'current_password' => 'victimpass123', 'password' => 'hacked123456' ]); - + $response->assertStatus(403); $result = json_decode($response->getJSON(), true); $this->assertFalse($result['success']); - + // Verify victim's password was NOT changed $employeeModel = model(Employee::class); - $victim = $employeeModel->get_info($victimId); - $this->assertTrue(password_verify('victimpass123', $victim->password), + $victim = $employeeModel->getInfo($victimId); + $this->assertTrue(password_verify('victimpass123', $victim->password), 'Non-admin should not be able to change another non-admin password'); } -} \ No newline at end of file +}