CVE-2021-3980 (PHP, published 2021-12-03T15:15Z)
Description: value: elgg is vulnerable to Exposure of Private Personal Information to an Unauthorized Actor
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-359 \u2014 Exposure of Private Personal Information to an Unauthorized Actor
The product does not properly prevent a person's private, personal information from being accessed by actors who either (1) are not explicitly authorized to access the information or (2) do not have the implicit consent of the person about whom the information is collected.
Repository: https://github.com/elgg/elgg
Files changed:
engine/lib/elgglib.php
engine/tests/phpunit/integration/Elgg/Ajax/ControllerIntegrationTest.php
engine/tests/test_files/views/default/admin/ajax_test/registered.php
engine/tests/test_files/views/default/ajax_test/not_registered.php
engine/tests/test_files/views/default/ajax_test/registered.php
engine/tests/test_files/views/default/forms/admin/ajax_test/registered.php
engine/tests/test_files/views/default/forms/ajax_test/not_registered.php
engine/tests/test_files/views/default/forms/ajax_test/registered.php
views/default/forms/admin/user/change_email.php
Commit message: fix(ajax): forms in the admin namespace are protected

All forms in the view namespace 'forms/admin' are now protected by an
AdminGatekeeper in case of AJAX requests to 'ajax/form/admin/'. This is
inline with the requests to 'ajax/view/admin'.

Vulnerable code:
if (!$user instanceof ElggUser) {

Fixed code:
			if ($segments[1] === 'admin') {
				// protect admin forms similar to all admin pages that are protected automatically in the admin_page_handler
				elgg_admin_gatekeeper();
			}
<?php

namespace Elgg\Ajax;

use Elgg\IntegrationTestCase;

class ControllerIntegrationTest extends IntegrationTestCase {

	/**
	 * @var \ElggUser
	 */
	protected $user;
	
	public function up() {
		$this->registerViews();
	}

	public function down() {
		elgg_get_session()->removeLoggedInUser();
		
		if (isset($this->user)) {
			elgg_call(ELGG_IGNORE_ACCESS, function() {
				$this->user->delete();
			});
		}
	}
	
	protected function prepareService(\Elgg\Http\Request $request) {
		$this->createApplication([
			'isolate' => true,
			'request' => $request,
		]);
		
		$this->registerViews();
	}
	
	protected function executeRequest(\Elgg\Http\Request $request, bool $prepare = true) {
		if ($prepare) {
			$this->prepareService($request);
		}
		
		return _elgg_services()->router->getResponse($request);
	}
	
	protected function registerViews() {
		$views_dir = $this->normalizeTestFilePath('views');
		_elgg_services()->views->autoregisterViews('', "{$views_dir}/default", 'default');
		
		elgg_register_ajax_view('ajax_test/registered');
		elgg_register_ajax_view('admin/ajax_test/registered');
		elgg_register_ajax_view('forms/ajax_test/registered');
		elgg_register_ajax_view('forms/admin/ajax_test/registered');
	}
	
	public function testCanLoadRegisteredAjaxView() {
		$request = $this->prepareHttpRequest('ajax/view/ajax_test/registered', 'GET', [], 1);
		
		$response = $this->executeRequest($request);
		$this->assertInstanceOf(\Elgg\Http\OkResponse::class, $response);
		$this->assertEquals('registered', $response->getContent());
	}
	
	public function testCantLoadRegisteredAjaxViewWithNonAjaxRequest() {
		$request = $this->prepareHttpRequest('ajax/view/ajax_test/registered');
		
		$this->expectException(\Elgg\Http\Exception\AjaxGatekeeperException::class);
		$this->executeRequest($request);
	}
	
	public function testCantLoadNonRegisteredAjaxView() {
		$this->assertTrue(elgg_view_exists('ajax_test/not_registered'));
		$request = $this->prepareHttpRequest('ajax/view/ajax_test/not_registered', 'GET', [], 1);
		
		$response = $this->executeRequest($request);
		$this->assertInstanceOf(\Elgg\Http\ErrorResponse::class, $response);
	}
	
	public function testCantAccessAdminAjaxViewLoggedOut() {
		$request = $this->prepareHttpRequest('ajax/view/admin/ajax_test/registered', 'GET', [], 1);
		
		$this->expectException(\Elgg\Http\Exception\LoggedInGatekeeperException::class);
		$this->executeRequest($request);
	}
	
	public function testCantAccessAdminAjaxViewAsNonAdmin() {
		$request = $this->prepareHttpRequest('ajax/view/admin/ajax_test/registered', 'GET', [], 1);
		
		$this->prepareService($request);
		
		$this->user = $user = $this->createUser();
		elgg_get_session()->setLoggedInUser($user);
		
		$this->expectException(\Elgg\Http\Exception\AdminGatekeeperException::class);
		$this->executeRequest($request, false);
	}
	
	public function testCanAccessAdminAjaxViewAsAdmin() {
		$request = $this->prepareHttpRequest('ajax/view/admin/ajax_test/registered', 'GET', [], 1);
		
		$this->prepareService($request);
		
		$user = $this->getAdmin();
		elgg_get_session()->setLoggedInUser($user);
		
		$response = $this->executeRequest($request, false);
		$this->assertInstanceOf(\Elgg\Http\OkResponse::class, $response);
		$this->assertEquals('registered', $response->getContent());
	}
	
	public function testCanLoadRegisteredAjaxForm() {
		$request = $this->prepareHttpRequest('ajax/form/ajax_test/registered', 'GET', [], 1);
		
		$response = $this->executeRequest($request);
		$this->assertInstanceOf(\Elgg\Http\OkResponse::class, $response);
		$this->assertStringContainsString('registered', $response->getContent());
	}
	
	public function testCantLoadRegisteredAjaxFormWithNonAjaxRequest() {
		$request = $this->prepareHttpRequest('ajax/form/ajax_test/registered');
		
		$this->expectException(\Elgg\Http\Exception\AjaxGatekeeperException::class);
		$this->executeRequest($request);
	}
	
	public function testCantLoadNonRegisteredAjaxForm() {
		$this->assertTrue(elgg_view_exists('ajax_test/not_registered'));
		$request = $this->prepareHttpRequest('ajax/form/ajax_test/not_registered', 'GET', [], 1);
		
		$response = $this->executeRequest($request);
		$this->assertInstanceOf(\Elgg\Http\ErrorResponse::class, $response);
	}
	
	public function testCantAccessAdminAjaxFormLoggedOut() {
		$request = $this->prepareHttpRequest('ajax/form/admin/ajax_test/registered', 'GET', [], 1);
		
		$this->expectException(\Elgg\Http\Exception\LoggedInGatekeeperException::class);
		$this->executeRequest($request);
	}
	
	public function testCantAccessAdminAjaxFormAsNonAdmin() {
		$request = $this->prepareHttpRequest('ajax/form/admin/ajax_test/registered', 'GET', [], 1);
		
		$this->prepareService($request);
		
		$this->user = $user = $this->createUser();
		elgg_get_session()->setLoggedInUser($user);
		
		$this->expectException(\Elgg\Http\Exception\AdminGatekeeperException::class);
		$this->executeRequest($request, false);
	}
	
	public function testCanAccessAdminAjaxFormAsAdmin() {
		$request = $this->prepareHttpRequest('ajax/form/admin/ajax_test/registered', 'GET', [], 1);
		
		$this->prepareService($request);
		
		$user = $this->getAdmin();
		elgg_get_session()->setLoggedInUser($user);
		
		$response = $this->executeRequest($request, false);
		$this->assertInstanceOf(\Elgg\Http\OkResponse::class, $response);
		$this->assertStringContainsString('registered', $response->getContent());
	}
}
<?php
/**
 * Registered ajax view
 */

echo 'registered';
<?php
/**
 * Non registered ajax view
 */

echo 'not registered';
<?php
/**
 * Registered ajax view
 */

echo 'registered';
<?php
/**
 * Registered ajax view
 */

echo 'registered';
<?php
/**
 * Non registered ajax view
 */

echo 'not registered';
<?php
/**
 * Registered ajax view
 */

echo 'registered';
if (!$user instanceof ElggUser || !$user->canEdit()) {