Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -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,
],
];
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}