diff options
author | Harald Eilertsen <haraldei@anduin.net> | 2024-11-02 14:42:00 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2024-11-02 14:42:00 +0000 |
commit | 38c947590e81fbb00e315e1902eba8dd6dbdd0ec (patch) | |
tree | b92d257beb82024c03f21f783c37169db9ec64c9 /tests/unit/Module/AdminAccountEditTest.php | |
parent | 541a0f6476ebf178ac141d09a30f6fca824eebcb (diff) | |
download | volse-hubzilla-38c947590e81fbb00e315e1902eba8dd6dbdd0ec.tar.gz volse-hubzilla-38c947590e81fbb00e315e1902eba8dd6dbdd0ec.tar.bz2 volse-hubzilla-38c947590e81fbb00e315e1902eba8dd6dbdd0ec.zip |
Fix missing CSRF checks in admin/account_edit
Diffstat (limited to 'tests/unit/Module/AdminAccountEditTest.php')
-rw-r--r-- | tests/unit/Module/AdminAccountEditTest.php | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/tests/unit/Module/AdminAccountEditTest.php b/tests/unit/Module/AdminAccountEditTest.php new file mode 100644 index 000000000..dab646a45 --- /dev/null +++ b/tests/unit/Module/AdminAccountEditTest.php @@ -0,0 +1,214 @@ +<?php +/* Tests for Account_edit module + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit\Module; + +use DateTimeImmutable; +use PHPUnit\Framework\Attributes\{Before, After}; +use Zotlabs\Model\Account; + +class AdminAccountEditTest extends TestCase { + + #[Before] + public function setup_mocks(): void { + /* + * As we're testing pages that should only be reachable by the + * site admin, it makes no sense to have it return anything else + * than true. + */ + $this->stub_is_site_admin = + $this->getFunctionMock('Zotlabs\Module', 'is_site_admin') + ->expects($this->once()) + ->willReturn(true); + + $this->info = []; + $this->stub_info = + $this->getFunctionMock('Zotlabs\Module\Admin', 'info') + ->expects($this->any()) + ->willReturnCallback(function (string $arg) { + $this->info[] = $arg; + }); + + $this->notice = []; + $this->stub_notice = + $this->getFunctionMock('Zotlabs\Module\Admin', 'notice') + ->expects($this->any()) + ->willReturnCallback(function (string $arg) { + $this->notice[] = $arg; + }); + + } + + #[After] + public function tear_down_mocks(): void { + $this->stub_is_site_admin = null; + $this->stub_info = null; + $this->stub_notice = null; + $this->stub_check_security = null; + $this->stub_get_form_security_token = null; + } + + public function test_rendering_admin_account_edit_page(): void { + $this->stub_get_form_security_token = + $this->getFunctionMock('Zotlabs\Module\Admin', 'get_form_security_token') + ->expects($this->once()) + ->willReturn('the-csrf-token'); + + $account = $this->fixtures['account'][0]; + + $this->get("admin/account_edit/{$account['account_id']}"); + + $this->assertPageContains("<form action=\"admin/account_edit/{$account['account_id']}\" method=\"post\""); + $this->assertPageContains($account['account_email']); + + // Check that we generate a CSRF token for the form + $this->assertPageContains("<input type=\"hidden\" name=\"security\" value=\"the-csrf-token\""); + } + + public function test_rendering_admin_account_edit_page_fails_if_id_is_not_found(): void { + $this->get("admin/account_edit/666"); + + $this->assertEquals('', \App::$page['content']); + } + + public function test_rendering_admin_account_edit_page_fails_if_id_is_not_numeric(): void { + $this->get("admin/account_edit/66invalid"); + + $this->assertEquals('', \App::$page['content']); + } + + public function test_post_empty_form_does_not_modify_account(): void { + $this->stub_goaway(); + $this->stub_check_form_security(true); + + $account = get_account_by_id($this->fixtures['account'][0]['account_id']); + + try { + $this->post( + "admin/account_edit/{$account['account_id']}", + [], + [ + 'aid' => $account['account_id'], + 'pass1' => '', + 'pass2' => '', + 'service_class' => $account['account_service_class'], + 'account_language' => $account['account_language'], + 'security' => 'The security token', + ] + ); + } catch (RedirectException $ex) { + $this->assertEquals(z_root() . '/admin/accounts', $ex->getMessage()); + } + + $reloaded = get_account_by_id($account['account_id']); + + $this->assertEquals($account, $reloaded); + + // Not sure if this is expected behaviour, but this is how it is today. + $this->assertContains('Account settings updated.' . EOL, $this->info); + } + + public function test_post_form_changes_account(): void { + $this->stub_goaway(); + $this->stub_check_form_security(true); + + // clone account from fixture, to ensure it's not replaced with + // the reloaded one below. + $account = get_account_by_id($this->fixtures['account'][0]['account_id']); + + try { + $this->post( + "admin/account_edit/{$account['account_id']}", + [], + [ + 'aid' => $account['account_id'], + 'pass1' => 'hunter2', + 'pass2' => 'hunter2', + 'service_class' => 'Some other class', + 'account_language' => 'nn', + 'security' => 'The security token', + ] + ); + } catch (RedirectException $ex) { + $this->assertEquals(z_root() . '/admin/accounts', $ex->getMessage()); + } + + $reloaded = get_account_by_id($account['account_id']); + + $this->assertNotEquals($account, $reloaded); + $this->assertEquals('Some other class', $reloaded['account_service_class']); + $this->assertEquals('nn', $reloaded['account_language']); + + $now = new DateTimeImmutable('now'); + $this->assertEquals($now->format('Y-m-d H:i:s'), $reloaded['account_password_changed']); + + $this->assertContains('Account settings updated.' . EOL, $this->info); + $this->assertContains("Password changed for account {$account['account_id']}." . EOL, $this->info); + } + + public function test_form_with_missing_or_incalid_csrf_token_is_rejected(): void { + $this->expectException(KillmeException::class); + + // Emulate a failed CSRF check + $this->stub_check_form_security(false); + + $account_id = $this->fixtures['account'][0]['account_id']; + + $this->post( + "admin/account_edit/{$account_id}", + [], + [ + 'aid' => $account_id, + 'pass1' => 'hunter2', + 'pass2' => 'hunter2', + 'service_class' => 'Some other class', + 'account_language' => 'nn', + 'security' => 'Invalid security token', + ] + ); + } + + /* + * Override the stub_goaway method because we need the stub to live in the + * Admin namespace. + */ + protected function stub_goaway(): void { + $this->goaway_stub = $this->getFunctionMock('Zotlabs\Module\Admin', 'goaway') + ->expects($this->once()) + ->willReturnCallback( + function (string $uri) { + throw new RedirectException($uri); + } + ); + } + + /** + * Stub the check_form_security_token_ForbiddenOnErr. + * + * In these tests we're not really interested in _how_ the form security + * tokens work, but that the code under test perform the checks. This stub + * allows us to do that without having to worry if everything is set up so + * that the real function would work or not. + * + * @param bool $valid true if emulating a valid token, false otherwise. + */ + protected function stub_check_form_security(bool $valid): void { + $this->stub_check_security = + $this->getFunctionMock('Zotlabs\Module\Admin', 'check_form_security_token_ForbiddenOnErr') + ->expects($this->once()) + ->with( + $this->identicalTo('admin_account_edit'), + $this->identicalTo('security')) + ->willReturnCallback(function () use ($valid) { + if (! $valid) { + throw new KillmeException(); + } + }); + } +} |