Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\UserBundle\Tests\Controller\Api;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
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;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
|
||||
class UserApiControllerFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testRoleUpdateByApiGivesErrorResponseIfUserDoesNotExist(): void
|
||||
{
|
||||
// Assuming user with id 99999 does not exist
|
||||
$this->client->request(Request::METHOD_PATCH, '/api/users/99999/edit', ['role' => 1]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_NOT_FOUND, $clientResponse->getStatusCode());
|
||||
Assert::assertStringContainsString('"message":"Item was not found."', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testRoleUpdateByApiGivesErrorResponseIfRoleDoesNotExist(): void
|
||||
{
|
||||
// Assuming role with id 99999 does not exist
|
||||
$this->client->request(Request::METHOD_PATCH, '/api/users/1/edit', ['role' => 99999]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_BAD_REQUEST, $clientResponse->getStatusCode());
|
||||
Assert::assertStringContainsString('"message":"role: The selected choice is invalid."', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testRoleUpdateByApiGivesErrorResponseWithInvalidRequestFormat(): void
|
||||
{
|
||||
// Correct request format is ['role' => 2]
|
||||
$this->client->request(Request::METHOD_PATCH, '/api/users/1/edit', ['role' => ['id' => 2]]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_BAD_REQUEST, $clientResponse->getStatusCode());
|
||||
Assert::assertStringContainsString('"message":"role: The selected choice is invalid."', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testRoleUpdateByApiGivesErrorResponseIfUserDoesNotHaveValidPermissionToUpdate(): void
|
||||
{
|
||||
// Create non-admin role
|
||||
$role = $this->createRole();
|
||||
// Create permissions for the role
|
||||
$this->createPermission('lead:leads:viewown', $role, 1024);
|
||||
// Create non-admin user
|
||||
$user = $this->createUser($role);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
// Login newly created non-admin user
|
||||
$this->loginUser($user);
|
||||
$this->client->setServerParameter('PHP_AUTH_USER', $user->getUserIdentifier());
|
||||
$this->client->setServerParameter('PHP_AUTH_PW', 'Maut1cR0cks!');
|
||||
|
||||
$this->client->request(Request::METHOD_PATCH, "/api/users/{$user->getId()}/edit", ['role' => $role->getId()]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_FORBIDDEN, $clientResponse->getStatusCode());
|
||||
Assert::assertStringContainsString(
|
||||
'"message":"You do not have access to the requested area\/action."',
|
||||
$clientResponse->getContent()
|
||||
);
|
||||
}
|
||||
|
||||
public function testRoleUpdateByApiThroughAdminUserGivesSuccessResponse(): void
|
||||
{
|
||||
// Create admin role
|
||||
$role = $this->createRole(true);
|
||||
// Create admin user
|
||||
$user = $this->createUser($role);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
// Login newly created admin user
|
||||
$this->loginUser($user);
|
||||
$this->client->setServerParameter('PHP_AUTH_USER', $user->getUserIdentifier());
|
||||
$this->client->setServerParameter('PHP_AUTH_PW', 'Maut1cR0cks!');
|
||||
|
||||
$this->client->request(Request::METHOD_PATCH, "/api/users/{$user->getId()}/edit", ['role' => $role->getId()]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_OK, $clientResponse->getStatusCode());
|
||||
Assert::assertStringContainsString('"username":"'.$user->getUserIdentifier().'"', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testRoleUpdateByApiThroughNonAdminUserGivesSuccessResponse(): void
|
||||
{
|
||||
// Create non-admin role
|
||||
$role = $this->createRole();
|
||||
// Create permissions to update user for the role
|
||||
$this->createPermission('user:users:edit', $role, 52);
|
||||
// Create non-admin user
|
||||
$user = $this->createUser($role);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->loginUser($user);
|
||||
$this->client->setServerParameter('PHP_AUTH_USER', $user->getUserIdentifier());
|
||||
$this->client->setServerParameter('PHP_AUTH_PW', 'Maut1cR0cks!');
|
||||
|
||||
$this->client->request(Request::METHOD_PATCH, "/api/users/{$user->getId()}/edit", ['role' => $role->getId()]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_OK, $clientResponse->getStatusCode());
|
||||
Assert::assertStringContainsString('"username":"'.$user->getUserIdentifier().'"', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testWeakPasswordGivesUnauthorizedResponse(): void
|
||||
{
|
||||
// Create non-admin role
|
||||
$role = $this->createRole();
|
||||
// Create permissions to update user for the role
|
||||
$this->createPermission('user:users:edit', $role, 52);
|
||||
// Create non-admin user with weak password.
|
||||
$weakPassword = 'mautic';
|
||||
$user = $this->createUser($role, $weakPassword);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->loginUser($user);
|
||||
$this->client->setServerParameter('PHP_AUTH_USER', $user->getUserIdentifier());
|
||||
$this->client->setServerParameter('PHP_AUTH_PW', $weakPassword);
|
||||
|
||||
$this->client->request(Request::METHOD_PATCH, "/api/users/{$user->getId()}/edit", ['role' => $role->getId()]);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame(Response::HTTP_UNAUTHORIZED, $clientResponse->getStatusCode());
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('passwordProvider')]
|
||||
public function testUserPasswordPolicy(int $responseCode, string $password): void
|
||||
{
|
||||
$userPayload = [
|
||||
'username' => 'lorem_ipsum',
|
||||
'firstName' => 'lorem',
|
||||
'lastName' => 'ipsum',
|
||||
'email' => 'loremipsum@example.com',
|
||||
'plainPassword' => ['password' => $password, 'confirm' => $password],
|
||||
'role' => 1,
|
||||
];
|
||||
|
||||
$this->client->request(Request::METHOD_POST, '/api/users/new', $userPayload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
Assert::assertSame($responseCode, $clientResponse->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array<int, mixed>>
|
||||
*/
|
||||
public static function passwordProvider(): iterable
|
||||
{
|
||||
yield [Response::HTTP_BAD_REQUEST, 'aaa'];
|
||||
yield [Response::HTTP_BAD_REQUEST, 'qwerty'];
|
||||
yield [Response::HTTP_BAD_REQUEST, 'qwerty123'];
|
||||
yield [Response::HTTP_CREATED, 'Qwertee@123'];
|
||||
}
|
||||
|
||||
private function createRole(bool $isAdmin = false): Role
|
||||
{
|
||||
$role = new Role();
|
||||
$role->setName('Role');
|
||||
$role->setIsAdmin($isAdmin);
|
||||
$this->em->persist($role);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
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 createUser(Role $role, string $password = 'Maut1cR0cks!'): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setFirstName('John');
|
||||
$user->setLastName('Doe');
|
||||
$user->setUsername('john.doe');
|
||||
$user->setEmail('john.doe@email.com');
|
||||
$hasher = self::getContainer()->get('security.password_hasher_factory')->getPasswordHasher($user);
|
||||
\assert($hasher instanceof PasswordHasherInterface);
|
||||
$user->setPassword($hasher->hash($password));
|
||||
$user->setRole($role);
|
||||
$this->em->persist($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a user via API Platform v2 endpoint.
|
||||
*
|
||||
* @param array<string, mixed> $userData
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('userCreateDataProvider')]
|
||||
public function testCreateUserViaApiPlatform(array $userData, int $expectedStatusCode): void
|
||||
{
|
||||
// Create a role first
|
||||
$role = new Role();
|
||||
$role->setName('Test Role');
|
||||
$role->setDescription('Test role for API');
|
||||
$this->em->persist($role);
|
||||
$this->em->flush();
|
||||
|
||||
// Set the role IRI in the user data
|
||||
$userData['role'] = sprintf('/api/v2/roles/%d', $role->getId());
|
||||
|
||||
$this->client->request(
|
||||
'POST',
|
||||
'/api/v2/users',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
'CONTENT_TYPE' => 'application/ld+json',
|
||||
'HTTP_ACCEPT' => 'application/ld+json',
|
||||
],
|
||||
json_encode($userData)
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
if (Response::HTTP_CREATED === $expectedStatusCode) {
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
|
||||
$this->assertIsArray($responseData);
|
||||
$this->assertArrayHasKey('id', $responseData);
|
||||
$this->assertArrayHasKey('username', $responseData);
|
||||
|
||||
// Verify the user was actually created in the database
|
||||
$userRepository = $this->em->getRepository(User::class);
|
||||
$user = $userRepository->find($responseData['id']);
|
||||
|
||||
$this->assertInstanceOf(User::class, $user);
|
||||
$this->assertSame($userData['username'], $user->getUsername());
|
||||
$this->assertSame($userData['firstName'], $user->getFirstName());
|
||||
$this->assertSame($userData['lastName'], $user->getLastName());
|
||||
$this->assertSame($userData['email'], $user->getEmail());
|
||||
|
||||
// Verify the password was hashed correctly by checking if we can verify it
|
||||
$hasher = self::getContainer()->get('security.password_hasher_factory')->getPasswordHasher($user);
|
||||
\assert($hasher instanceof PasswordHasherInterface);
|
||||
$this->assertTrue(
|
||||
$hasher->verify($user->getPassword(), $userData['plainPassword']),
|
||||
'Password should be properly hashed and verifiable'
|
||||
);
|
||||
|
||||
// Verify we can log in with the new user (simulates authentication)
|
||||
$this->loginUser($user);
|
||||
$this->client->request('GET', '/s/dashboard');
|
||||
|
||||
// Assert we can access the dashboard successfully
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertStringContainsString('/s/dashboard', $this->client->getRequest()->getRequestUri());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{userData: array<string, mixed>, expectedStatusCode: int}>
|
||||
*/
|
||||
public static function userCreateDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'valid user with password' => [
|
||||
'userData' => [
|
||||
'username' => 'john',
|
||||
'plainPassword' => 'jjohn@123',
|
||||
'firstName' => 'John',
|
||||
'lastName' => 'Doe',
|
||||
'email' => 'john.doe@email.com',
|
||||
],
|
||||
'expectedStatusCode' => Response::HTTP_CREATED,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\UserBundle\Tests\Controller;
|
||||
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionFactory;
|
||||
|
||||
trait LoginUserWithSamlTrait
|
||||
{
|
||||
private function loginUserWithSaml(User $user): void
|
||||
{
|
||||
$firewallContext = 'mautic';
|
||||
$token = new TestBrowserToken($user->getRoles(), $user, $firewallContext);
|
||||
$container = $this->getContainer();
|
||||
$container->get('security.untracked_token_storage')->setToken($token);
|
||||
|
||||
$session = self::getContainer()->get('session.factory')->createSession();
|
||||
$session->set('samlsso', true);
|
||||
$session->set('_security_'.$firewallContext, serialize($token));
|
||||
$session->save();
|
||||
|
||||
$sessionFactory = $this->createMock(SessionFactory::class);
|
||||
$sessionFactory->expects($this->any())
|
||||
->method('createSession')
|
||||
->willReturn($session);
|
||||
|
||||
self::getContainer()->set('session.factory', $sessionFactory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\UserBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\UserBundle\Tests\Traits\CreateEntityTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class ProfileControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
use CreateEntityTrait;
|
||||
use LoginUserWithSamlTrait;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
if (strpos($this->name(), 'WithSaml') > 0) {
|
||||
$this->configParams['saml_idp_metadata'] = 'any_string';
|
||||
}
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testPasswordNotOnAccountPageWithSaml(): void
|
||||
{
|
||||
$user = $this->createUser($this->createRole(), 'test@example.com');
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->loginUserWithSaml($user);
|
||||
|
||||
$this->client->request(Request::METHOD_GET, 's/account');
|
||||
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertEquals(200, $clientResponse->getStatusCode());
|
||||
$this->assertStringNotContainsString('user[plainPassword][password]', $clientResponse->getContent());
|
||||
$this->assertStringNotContainsString('user[plainPassword][confirm]', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testPasswordOnAccountPageWithoutSaml(): void
|
||||
{
|
||||
$user = $this->createUser($this->createRole(), 'test@example.com');
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
$this->loginUser($user);
|
||||
|
||||
$this->client->request(Request::METHOD_GET, 's/account');
|
||||
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertEquals(200, $clientResponse->getStatusCode());
|
||||
$this->assertStringContainsString('user[plainPassword][password]', $clientResponse->getContent());
|
||||
$this->assertStringContainsString('user[plainPassword][confirm]', $clientResponse->getContent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\UserBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class SecurityControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
if (strpos($this->name(), 'WithSaml') > 0) {
|
||||
$this->configParams['saml_idp_metadata'] = 'any_string';
|
||||
}
|
||||
|
||||
parent::setUp();
|
||||
$this->logoutUser();
|
||||
}
|
||||
|
||||
public function testLoginRetryPageShowsErrorWithSaml(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/saml/login_retry');
|
||||
|
||||
$clientResponse = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(200, $clientResponse->getStatusCode());
|
||||
|
||||
$validationError = self::getContainer()->get('translator')->trans('mautic.user.security.saml.clearsession', [], 'flashes');
|
||||
$this->assertStringContainsString($validationError, $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testLoginRetryPageRedirectsToLoginWithoutSaml(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/saml/login_retry');
|
||||
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertEquals(200, $clientResponse->getStatusCode());
|
||||
|
||||
$validationError = self::getContainer()->get('translator')->trans('mautic.user.security.saml.clearsession', [], 'flashes');
|
||||
$this->assertStringNotContainsString($validationError, $clientResponse->getContent());
|
||||
|
||||
$loginText = self::getContainer()->get('translator')->trans('mautic.user.auth.form.loginbtn', [], 'messages');
|
||||
$this->assertStringContainsString($loginText, $clientResponse->getContent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\UserBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\UserBundle\Tests\Traits\CreateEntityTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class UserControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
use CreateEntityTrait;
|
||||
use LoginUserWithSamlTrait;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
if (strpos($this->name(), 'WithSaml') > 0) {
|
||||
$this->configParams['saml_idp_metadata'] = 'any_string';
|
||||
}
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testPasswordFieldsOnEditUserPageWithSaml(): void
|
||||
{
|
||||
$user1 = $this->createUser($this->createRole(), 'test2@example.com');
|
||||
$user2 = $this->createUser($this->createRole(true), 'test@example.com');
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->loginUserWithSaml($user2);
|
||||
|
||||
$this->client->request(Request::METHOD_GET, 's/users/edit/'.$user1->getId());
|
||||
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertEquals(200, $clientResponse->getStatusCode());
|
||||
$this->assertStringNotContainsString('user[plainPassword][password]', $clientResponse->getContent());
|
||||
$this->assertStringNotContainsString('user[plainPassword][confirm]', $clientResponse->getContent());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user