Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Role export access in Leads, Forms, Reports (#12884)
* Role export access in Leads, Forms, Reports * Refactor to enable permission for export * Fix PHP Stan * Add migration * CS fixes * Rector fixes * Add unit tests * Fix PHP Stan with Rector * Fix PHP Stan issues * Refactor addCustomPermission to specify parameter types. * Add permission check before adding export button to contact index page. * Refactor isAdmin() method to specify return type as bool. * Support export permission for export of single contact csv * Remove unnecessary dependencies from lead bundle config. * Add missing permissions to existing roles during migration. * Add 'postAction' key to test form data array. * Fixed indentation in list.html.twig and added conditional logic for export buttons based on permissions and class existence. * Fixed syntax error in setting buttons array in list.html.twig --------- Co-authored-by: John Linhart <admin@escope.cz> Co-authored-by: Ruth Cheesley <ruth@ruthcheesley.co.uk>
- Loading branch information
1 parent
35d288d
commit aad47c7
Showing
16 changed files
with
426 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
218 changes: 218 additions & 0 deletions
218
app/bundles/CoreBundle/Tests/Functional/Controller/ExportControllerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Mautic\CoreBundle\Tests\Functional\Controller; | ||
|
||
use Mautic\CoreBundle\Test\MauticMysqlTestCase; | ||
use Mautic\LeadBundle\Entity\Lead; | ||
use Mautic\ReportBundle\Entity\Report; | ||
use Mautic\UserBundle\Entity\Permission; | ||
use Mautic\UserBundle\Entity\Role; | ||
use Mautic\UserBundle\Entity\User; | ||
use PHPUnit\Framework\Assert; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
final class ExportControllerTest extends MauticMysqlTestCase | ||
{ | ||
protected $useCleanupRollback = false; | ||
|
||
public const PERMISSION_LEAD_EXPORT = 'lead:export:enable'; | ||
public const PERMISSION_FORM_EXPORT = 'form:export:enable'; | ||
public const PERMISSION_REPORT_EXPORT = 'report:export:enable'; | ||
|
||
public function testContactExportAction(): void | ||
{ | ||
$permissions = [ | ||
1024 => 'lead:export:enable', | ||
34 => 'lead:leads:create', | ||
]; | ||
$this->createAndLoginUser($permissions); | ||
|
||
$this->client->request(Request::METHOD_GET, '/s/contacts'); | ||
$this->assertStringContainsString('Export to CSV', $this->client->getResponse()->getContent()); | ||
$this->client->request(Request::METHOD_GET, '/s/contacts/batchExport?filetype=csv'); | ||
Assert::assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode(), $this->client->getResponse()->getContent()); | ||
} | ||
|
||
public function testFormExportAction(): void | ||
{ | ||
$permissions = [ | ||
1024 => 'form:export:enable', | ||
34 => 'form:forms:create', | ||
]; | ||
$this->createAndLoginUser($permissions); | ||
|
||
$formId = $this->createForm(); | ||
|
||
$this->client->request(Request::METHOD_GET, '/s/forms/results/'.$formId); | ||
$this->assertStringContainsString('Export to CSV', $this->client->getResponse()->getContent()); | ||
$this->client->request(Request::METHOD_GET, '/s/forms/results/'.$formId.'/export'); | ||
Assert::assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode()); | ||
} | ||
|
||
public function testReportExportAction(): void | ||
{ | ||
$permissions = [ | ||
1024 => 'report:export:enable', | ||
34 => 'lead:leads:create', | ||
36 => 'report:reports:create', | ||
]; | ||
$this->createAndLoginUser($permissions); | ||
|
||
$contact = new Lead(); | ||
$contact->setDateAdded(new \DateTime()); | ||
|
||
$this->em->persist($contact); | ||
$this->em->flush(); | ||
|
||
$report = new Report(); | ||
$report->setName('Contact report'); | ||
$report->setSource('leads'); | ||
$coulmns = [ | ||
'l.id', | ||
]; | ||
$report->setColumns($coulmns); | ||
|
||
$this->getContainer()->get('mautic.report.model.report')->saveEntity($report); | ||
|
||
// Check the details page | ||
$this->client->request('GET', '/s/reports/view/'.$report->getId()); | ||
Assert::assertTrue($this->client->getResponse()->isOk()); | ||
|
||
$this->client->request(Request::METHOD_GET, '/s/reports/view/'.$report->getId().''); | ||
$this->assertStringContainsString('Export to CSV', $this->client->getResponse()->getContent()); | ||
$this->client->request(Request::METHOD_GET, '/s/reports/view/'.$report->getId().'/export'); | ||
Assert::assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode()); | ||
} | ||
|
||
/** | ||
* @param array<string|int> $permissions | ||
*/ | ||
private function createAndLoginUser(array $permissions): User | ||
{ | ||
// Create non-admin role | ||
$role = $this->createRole(); | ||
// Create permissions to update user for the role | ||
foreach ($permissions as $bitwise => $permission) { | ||
$this->createPermission($permission, $role, $bitwise); | ||
} | ||
// Create non-admin user | ||
$user = $this->createUser($role); | ||
|
||
$this->em->flush(); | ||
$this->em->detach($role); | ||
/** @phpstan-ignore-next-line */ | ||
$this->loginUser($user->getUsername()); | ||
/** @phpstan-ignore-next-line */ | ||
$this->client->setServerParameter('PHP_AUTH_USER', $user->getUsername()); | ||
$this->client->setServerParameter('PHP_AUTH_PW', 'mautic'); | ||
|
||
return $user; | ||
} | ||
|
||
private function createPermission(string $rawPermission, Role $role, int $bitwise): void | ||
{ | ||
$parts = explode(':', $rawPermission); | ||
$permission = new Permission(); | ||
$permission->setBundle($parts[0]); | ||
$permission->setName($parts[1]); | ||
$permission->setRole($role); | ||
$permission->setBitwise($bitwise); | ||
|
||
$this->em->persist($permission); | ||
} | ||
|
||
private function createRole(bool $isAdmin = false): Role | ||
{ | ||
$role = new Role(); | ||
$role->setName('Role'); | ||
$role->setIsAdmin($isAdmin); | ||
|
||
$this->em->persist($role); | ||
|
||
return $role; | ||
} | ||
|
||
private function createUser(Role $role): User | ||
{ | ||
$user = new User(); | ||
$user->setFirstName('John'); | ||
$user->setLastName('Doe'); | ||
$user->setUsername('john.doe'); | ||
$user->setEmail('john.doe@email.com'); | ||
$encoder = self::$container->get('security.encoder_factory')->getEncoder($user); | ||
$user->setPassword($encoder->encodePassword('mautic', null)); | ||
$user->setRole($role); | ||
|
||
$this->em->persist($user); | ||
|
||
return $user; | ||
} | ||
|
||
private function createForm(): int | ||
{ | ||
$formPayload = [ | ||
'name' => 'Test Form', | ||
'formType' => 'standalone', | ||
'description' => 'API test', | ||
'postAction' => 'return', | ||
'fields' => [ | ||
[ | ||
'label' => 'firstname', | ||
'alias' => 'firstname', | ||
'type' => 'text', | ||
], | ||
[ | ||
'label' => 'email', | ||
'alias' => 'email', | ||
'type' => 'email', | ||
'leadField' => 'email', | ||
], | ||
[ | ||
'label' => 'description', | ||
'alias' => 'description', | ||
'type' => 'textarea', | ||
], | ||
[ | ||
'label' => 'checkbox', | ||
'alias' => 'checkbox', | ||
'type' => 'checkboxgrp', | ||
'properties' => [ | ||
'syncList' => 0, | ||
'optionlist' => [ | ||
'list' => [ | ||
[ | ||
'label' => 'val1', | ||
'value' => 'val1', | ||
], | ||
[ | ||
'label' => 'val2', | ||
'value' => 'val2', | ||
], | ||
[ | ||
'label' => 'val3', | ||
'value' => 'val3', | ||
], | ||
], | ||
], | ||
'labelAttributes' => null, | ||
], | ||
], | ||
[ | ||
'label' => 'Submit', | ||
'alias' => 'submit', | ||
'type' => 'button', | ||
], | ||
], | ||
]; | ||
|
||
$this->client->request('POST', '/api/forms/new', $formPayload); | ||
$clientResponse = $this->client->getResponse(); | ||
$this->assertEquals(Response::HTTP_CREATED, $clientResponse->getStatusCode(), $clientResponse->getContent()); | ||
$response = json_decode($clientResponse->getContent(), true); | ||
|
||
return $response['form']['id']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.