Skip to content

Commit cd9a102

Browse files
authored
Merge pull request #412 from systopia/ensure-admins-can-see-all-funding-cases
Allow admins to see funding cases in CiviCRM even though they have applicant permissions
2 parents 7d8956f + 3a8c712 commit cd9a102

File tree

6 files changed

+88
-8
lines changed

6 files changed

+88
-8
lines changed

Civi/Funding/EventSubscriber/FundingCase/FundingCaseFilterPermissionsSubscriber.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
namespace Civi\Funding\EventSubscriber\FundingCase;
2121

22+
use Civi\Funding\Api4\Permissions;
2223
use Civi\Funding\Event\FundingCase\GetPermissionsEvent;
24+
use Civi\Funding\Permission\CiviPermissionChecker;
2325
use Civi\RemoteTools\RequestContext\RequestContextInterface;
2426
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2527

@@ -31,6 +33,8 @@ final class FundingCaseFilterPermissionsSubscriber implements EventSubscriberInt
3133
'clearing_',
3234
];
3335

36+
private CiviPermissionChecker $permissionChecker;
37+
3438
private RequestContextInterface $requestContext;
3539

3640
/**
@@ -42,7 +46,8 @@ public static function getSubscribedEvents(): array {
4246
];
4347
}
4448

45-
public function __construct(RequestContextInterface $requestContext) {
49+
public function __construct(CiviPermissionChecker $permissionChecker, RequestContextInterface $requestContext) {
50+
$this->permissionChecker = $permissionChecker;
4651
$this->requestContext = $requestContext;
4752
}
4853

@@ -75,11 +80,16 @@ private function provideOnlyApplicantPermissions(GetPermissionsEvent $event): vo
7580
private function preventAccessIfHasApplicantPermission(GetPermissionsEvent $event): void {
7681
foreach ($event->getPermissions() as $permission) {
7782
if ($this->isApplicantPermission($permission)) {
78-
$event->setPermissions([]);
83+
// funding admins are still allowed to view the application.
84+
$event->setPermissions($this->isFundingAdmin() ? ['view'] : []);
7985

8086
return;
8187
}
8288
}
8389
}
8490

91+
private function isFundingAdmin(): bool {
92+
return $this->permissionChecker->checkPermission(Permissions::ADMINISTER_FUNDING);
93+
}
94+
8595
}

Civi/Funding/EventSubscriber/FundingCase/FundingCasePermissionsGetAdminSubscriber.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
use Civi\Funding\Api4\Permissions;
2323
use Civi\Funding\Event\FundingCase\GetPermissionsEvent;
24+
use Civi\Funding\Permission\CiviPermissionChecker;
2425
use Civi\RemoteTools\RequestContext\RequestContextInterface;
2526
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2627

@@ -32,6 +33,8 @@
3233
*/
3334
final class FundingCasePermissionsGetAdminSubscriber implements EventSubscriberInterface {
3435

36+
private CiviPermissionChecker $permissionChecker;
37+
3538
private RequestContextInterface $requestContext;
3639

3740
/**
@@ -41,7 +44,8 @@ public static function getSubscribedEvents(): array {
4144
return [GetPermissionsEvent::class => 'onPermissionsGet'];
4245
}
4346

44-
public function __construct(RequestContextInterface $requestContext) {
47+
public function __construct(CiviPermissionChecker $permissionChecker, RequestContextInterface $requestContext) {
48+
$this->permissionChecker = $permissionChecker;
4549
$this->requestContext = $requestContext;
4650
}
4751

@@ -61,7 +65,7 @@ private function isFundingAdmin(): bool {
6165
return PHP_SAPI === 'cli';
6266
}
6367

64-
return \CRM_Core_Permission::check(Permissions::ADMINISTER_FUNDING, $this->requestContext->getContactId());
68+
return $this->permissionChecker->checkPermission(Permissions::ADMINISTER_FUNDING);
6569
}
6670

6771
}

Civi/Funding/EventSubscriber/FundingProgram/FundingProgramPermissionsGetAdminSubscriber.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
use Civi\Funding\Api4\Permissions;
2323
use Civi\Funding\Event\FundingProgram\GetPermissionsEvent;
24+
use Civi\Funding\Permission\CiviPermissionChecker;
2425
use Civi\RemoteTools\RequestContext\RequestContextInterface;
2526
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2627

@@ -32,6 +33,8 @@
3233
*/
3334
final class FundingProgramPermissionsGetAdminSubscriber implements EventSubscriberInterface {
3435

36+
private CiviPermissionChecker $permissionChecker;
37+
3538
private RequestContextInterface $requestContext;
3639

3740
/**
@@ -41,7 +44,8 @@ public static function getSubscribedEvents(): array {
4144
return [GetPermissionsEvent::class => 'onPermissionsGet'];
4245
}
4346

44-
public function __construct(RequestContextInterface $requestContext) {
47+
public function __construct(CiviPermissionChecker $permissionChecker, RequestContextInterface $requestContext) {
48+
$this->permissionChecker = $permissionChecker;
4549
$this->requestContext = $requestContext;
4650
}
4751

@@ -61,7 +65,7 @@ private function isFundingAdmin(): bool {
6165
return PHP_SAPI === 'cli';
6266
}
6367

64-
return \CRM_Core_Permission::check(Permissions::ADMINISTER_FUNDING, $this->requestContext->getContactId());
68+
return $this->permissionChecker->checkPermission(Permissions::ADMINISTER_FUNDING);
6569
}
6670

6771
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/*
3+
* Copyright (C) 2025 SYSTOPIA GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation in version 3.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
declare(strict_types = 1);
19+
20+
namespace Civi\Funding\Permission;
21+
22+
use Civi\RemoteTools\RequestContext\RequestContextInterface;
23+
24+
class CiviPermissionChecker {
25+
26+
private RequestContextInterface $requestContext;
27+
28+
public function __construct(RequestContextInterface $requestContext) {
29+
$this->requestContext = $requestContext;
30+
}
31+
32+
/**
33+
* @phpstan-param string|list<string|list<string>> $permissions
34+
*
35+
* @see \CRM_Core_Permission::check()
36+
*/
37+
public function checkPermission($permissions, ?int $contactId = NULL): bool {
38+
return \CRM_Core_Permission::check($permissions, $contactId ?? $this->requestContext->getContactId());
39+
}
40+
41+
}

services/other.php

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
use Civi\Funding\ExternalFile\FundingExternalFileManagerInterface;
4141
use Civi\Funding\FundingAttachmentManager;
4242
use Civi\Funding\FundingAttachmentManagerInterface;
43+
use Civi\Funding\Permission\CiviPermissionChecker;
4344
use Civi\Funding\Util\MoneyFactory;
4445
use Civi\Funding\Util\UrlGenerator;
4546
use Civi\Funding\Validation\EntityValidator;
@@ -65,6 +66,7 @@
6566
$container->autowire(MoneyFactory::class);
6667
$container->autowire(ChangeSetFactory::class);
6768
$container->autowire(DaoEntityInfoProvider::class);
69+
$container->autowire(CiviPermissionChecker::class);
6870

6971
$container->addCompilerPass(new ActionPropertyAutowireFixPass(), PassConfig::TYPE_BEFORE_REMOVING);
7072

tests/phpunit/Civi/Funding/EventSubscriber/FundingCase/FundingCaseFilterPermissionsSubscriberTest.php

+21-2
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,32 @@
1919

2020
namespace Civi\Funding\EventSubscriber\FundingCase;
2121

22+
use Civi\Funding\Api4\Permissions;
2223
use Civi\Funding\Event\FundingCase\GetPermissionsEvent;
2324
use Civi\Funding\Mock\RequestContext\TestRequestContext;
25+
use Civi\Funding\Permission\CiviPermissionChecker;
26+
use PHPUnit\Framework\MockObject\MockObject;
2427
use PHPUnit\Framework\TestCase;
2528

2629
/**
2730
* @covers \Civi\Funding\EventSubscriber\FundingCase\FundingCaseFilterPermissionsSubscriber
2831
*/
2932
final class FundingCaseFilterPermissionsSubscriberTest extends TestCase {
3033

34+
/**
35+
* @var \Civi\Funding\Permission\CiviPermissionChecker&\PHPUnit\Framework\MockObject\MockObject
36+
*/
37+
private MockObject $permissionCheckerMock;
38+
3139
private FundingCaseFilterPermissionsSubscriber $subscriber;
3240

3341
private TestRequestContext $requestContext;
3442

3543
protected function setUp(): void {
3644
parent::setUp();
45+
$this->permissionCheckerMock = $this->createMock(CiviPermissionChecker::class);
3746
$this->requestContext = TestRequestContext::newInternal();
38-
$this->subscriber = new FundingCaseFilterPermissionsSubscriber($this->requestContext);
47+
$this->subscriber = new FundingCaseFilterPermissionsSubscriber($this->permissionCheckerMock, $this->requestContext);
3948
}
4049

4150
public function testGetSubscribedEvents(): void {
@@ -59,12 +68,22 @@ public function testExcludesNonApplicantPermissionsRemoteRequest(): void {
5968
}
6069

6170
public function testPreventAccessInternalRequest(): void {
62-
$event = $this->createEvent(['application_foo', 'review_bar']);
71+
$event = $this->createEvent(['application_foo', 'review_bar', 'view']);
6372

6473
$this->subscriber->onPermissionsGet($event);
6574
static::assertSame([], $event->getPermissions());
6675
}
6776

77+
public function testFundingAdminCanViewCasesWithApplicantPermissions(): void {
78+
$event = $this->createEvent(['application_foo', 'review_bar', 'view']);
79+
$this->permissionCheckerMock->method('checkPermission')
80+
->with(Permissions::ADMINISTER_FUNDING)
81+
->willReturn(TRUE);
82+
83+
$this->subscriber->onPermissionsGet($event);
84+
static::assertSame(['view'], $event->getPermissions());
85+
}
86+
6887
public function testInternalRequest(): void {
6988
$event = $this->createEvent(['review_bar']);
7089

0 commit comments

Comments
 (0)