"'
10 | mysql -e 'GRANT ALL PRIVILEGES ON userli.* TO `userli`@`localhost`'
11 | ```
12 |
--------------------------------------------------------------------------------
/docs/installation/index.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Requirements
4 |
5 | * Webserver (e.g [Caddy](https://caddyserver.com/))
6 | * [PHP >= 8.0](https://secure.php.net/) with libsodium
7 | * [MariaDB](https://mariadb.org/) or [MySQL](https://mysql.com/)
8 | * [OpenSSL](https://www.openssl.org/) binary (for MailCrypt feature)
9 | * [GnuPG](https://gnupg.org/) version 2.1.14 or newer
10 |
11 | You can also run this application with PostgreSQL oder SQLite.
12 |
--------------------------------------------------------------------------------
/docs/screenshots/index.md:
--------------------------------------------------------------------------------
1 | # Screenshots
2 |
3 | Some screenshots of Userli features
4 |
5 | ## Account management
6 |
7 | Change password and delete account
8 |
9 | 
10 |
11 |
12 | ## Admin Frontend
13 |
14 | Manage domains, users, aliases and more
15 |
16 | 
17 |
18 | ## Invite friends
19 |
20 | Invite codes
21 |
22 | 
23 |
24 | ## Alias addresses
25 |
26 | Manage alias addresses
27 |
28 | 
29 |
30 | ## Recover lost password
31 |
32 | Manage recovery token
33 |
34 | 
35 |
36 | Add recovery token
37 |
38 | 
39 |
40 | Use recovery token
41 |
42 | 
43 |
--------------------------------------------------------------------------------
/docs/update/index.md:
--------------------------------------------------------------------------------
1 | # Update
2 |
3 | When updating to a new userli version, please take a look at `UPGRADE.md`
4 | to see whether manual steps are required.
5 |
6 | To automatically update the database schema of userli, run these commands:
7 |
8 | ```shell
9 | # Warm up cache
10 | bin/console cache:warmup
11 |
12 | # Show database schema updates
13 | bin/console doctrine:schema:update --dump-sql
14 |
15 | # If necessary update the database schema
16 | bin/console doctrine:schema:update --force
17 | ```
18 |
--------------------------------------------------------------------------------
/features/init.feature:
--------------------------------------------------------------------------------
1 | Feature: Initialization
2 |
3 | Background:
4 | Given the database is clean
5 |
6 | @init
7 | Scenario: Redirect to init site
8 | When I am on homepage
9 |
10 | Then I should be on "/init"
11 |
12 | @init
13 | Scenario: Input admin password
14 | When the following Domain exists:
15 | | name |
16 | | example.org |
17 | And I am on "/init/user"
18 | And I fill in the following:
19 | | plain_password[plainPassword][first] | P4ssW0rt!!!1 |
20 | | plain_password[plainPassword][second] | P4ssW0rt!!!1 |
21 | And I press "Submit"
22 |
23 | Then I should be on "/"
24 |
25 | @init
26 | Scenario: No more redirect to init site
27 | When the following Domain exists:
28 | | name |
29 | | example.org |
30 | And the following User exists:
31 | | email | password |
32 | | postmaster@example.org | P4ssW0rt |
33 | And I am on homepage
34 |
35 | Then I should be on "/"
36 |
--------------------------------------------------------------------------------
/features/language.feature:
--------------------------------------------------------------------------------
1 | Feature: Language detection
2 |
3 | Background:
4 | Given the database is clean
5 | And the following Domain exists:
6 | | name |
7 | | example.org |
8 | And the following User exists:
9 | | email | password | roles |
10 | | postmaster@example.org | asdasd | ROLE_ADMIN |
11 |
12 | @language
13 | Scenario: Default language
14 | When I am on "/"
15 | Then I should see text matching "Welcome"
16 | Then I am on "/?_locale=de"
17 | Then I should see text matching "Willkommen"
18 | Then I am on "/"
19 | Then I should see text matching "Willkommen"
20 |
21 | @language
22 | Scenario: Session language
23 | When I am on "/?_locale=de"
24 | Then I should see text matching "Willkommen"
25 | And I am on "/"
26 | And I should see text matching "Willkommen"
27 |
28 | @language
29 | Scenario: Browser language detection
30 | Given set the HTTP-Header "Accept-Language" to "de"
31 | When I am on "/"
32 | Then I should see text matching "Willkommen"
33 |
34 | @language
35 | Scenario: Browser language fallback
36 | Given set the HTTP-Header "Accept-Language" to "afa"
37 | When I am on "/"
38 | Then I should see text matching "Welcome"
39 |
--------------------------------------------------------------------------------
/features/quotaCommand.feature:
--------------------------------------------------------------------------------
1 | Feature: QuotaCommand
2 |
3 | Background:
4 | Given the database is clean
5 | And the following Domain exists:
6 | | name |
7 | | example.org |
8 | And the following User exists:
9 | | email | password | quota |
10 | | noquota@example.org | password | |
11 | | quota@example.org | password | 1000 |
12 |
13 | @quotaCommand
14 | Scenario: Check that user has quota
15 | When I run console command "app:users:quota --user quota@example.org"
16 | Then I should see "1000" in the console output
17 |
18 | When I run console command "app:users:quota --user noquota@example.org"
19 | Then I should not see "1000" in the console output
20 |
--------------------------------------------------------------------------------
/features/voucherCreateCommand.feature:
--------------------------------------------------------------------------------
1 | Feature: VoucherCreateCommand
2 |
3 | Background:
4 | Given the database is clean
5 | And the following Domain exists:
6 | | name |
7 | | example.org |
8 | And the following User exists:
9 | | email | password |
10 | | user@example.org | password |
11 |
12 | @voucherCreationCommand
13 | Scenario: Create new voucher
14 | When I run console command "app:voucher:create --user user@example.org -c 1 -p"
15 | Then I should see regex "|^[a-z_\-0-9]{6}$|i" in the console output
16 |
17 | When I run console command "app:voucher:create --user user@example.org -c 1 -l"
18 | Then I should see regex "|^https://users.example.org/register/[a-z_\-0-9]{6}$|i" in the console output
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev-server": "encore dev-server",
4 | "dev": "encore dev",
5 | "watch": "encore dev --watch",
6 | "build": "encore production --progress"
7 | },
8 | "devDependencies": {
9 | "@babel/core": "^7.26.10",
10 | "@babel/preset-env": "^7.26.9",
11 | "@hotwired/stimulus": "^3.0.0",
12 | "@symfony/stimulus-bridge": "^4.0.0",
13 | "@symfony/webpack-encore": "^5.1.0",
14 | "copy-webpack-plugin": "^13.0.0",
15 | "jquery": "^3.7.1",
16 | "webpack": "^5.99.5",
17 | "webpack-cli": "^6.0.1"
18 | },
19 | "packageManager": "yarn@1.22.19"
20 | }
21 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/systemli/userli/5e0025bd53553729e46c75b4d750fbf5d2eff9fd/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | paths([
11 | __DIR__ . '/src',
12 | __DIR__ . '/tests',
13 | ]);
14 | $rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml');
15 | $rectorConfig->importNames();
16 | $rectorConfig->phpVersion(\Rector\ValueObject\PhpVersion::PHP_81);
17 |
18 | $rectorConfig->skip([
19 | // SonataAdminBundle CRUDController needs the suffix for actions
20 | ActionSuffixRemoverRector::class => [
21 | __DIR__ . '/src/Controller/AliasCRUDController.php',
22 | __DIR__ . '/src/Controller/UserCRUDController.php',
23 | ],
24 | ]);
25 |
26 | $rectorConfig->sets([
27 | SetList::PHP_81,
28 | SymfonySetList::SYMFONY_CODE_QUALITY,
29 | SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
30 | SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
31 | DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
32 | ]);
33 | };
34 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=systemli_userli
2 | sonar.organization=systemli
3 |
4 | sonar.sources=src/
5 | sonar.tests=tests/
6 | sonar.exclusions=src/DataFixtures/**
7 |
8 | sonar.php.tests.reportPath=build/result-phpunit.xml
9 | sonar.php.coverage.reportPaths=build/clover-phpunit.xml,build/clover-behat.xml
10 | sonar.php.file.suffixes=php
11 |
--------------------------------------------------------------------------------
/src/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/src/Admin/Admin.php:
--------------------------------------------------------------------------------
1 | getRequest()->get($this->getIdParameter());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Builder/AliasCreatedMessageBuilder.php:
--------------------------------------------------------------------------------
1 | translator->trans(
22 | 'mail.alias-created-body',
23 | [
24 | '%app_url%' => $this->appUrl,
25 | '%project_name%' => $this->projectName,
26 | '%email%' => $email,
27 | '%alias%' => $alias,
28 | ],
29 | null,
30 | $locale
31 | );
32 | }
33 |
34 | public function buildSubject(string $locale, string $email): string
35 | {
36 | return $this->translator->trans(
37 | 'mail.alias-created-subject', ['%email%' => $email], null, $locale
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Builder/RecoveryProcessMessageBuilder.php:
--------------------------------------------------------------------------------
1 | translator->trans(
22 | 'mail.recovery-body',
23 | [
24 | '%app_url%' => $this->appUrl,
25 | '%project_name%' => $this->projectName,
26 | '%email%' => $email,
27 | '%time%' => $time,
28 | ],
29 | null,
30 | $locale
31 | );
32 | }
33 |
34 | public function buildSubject(string $locale, string $email): string
35 | {
36 | return $this->translator->trans(
37 | 'mail.recovery-subject', ['%email%' => $email], null, $locale
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Command/AbstractUsersCommand.php:
--------------------------------------------------------------------------------
1 | addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User to act upon')
24 | ->addOption('dry-run', null, InputOption::VALUE_NONE);
25 | }
26 |
27 | /**
28 | * @throws UserNotFoundException
29 | */
30 | protected function getUser(InputInterface $input): User
31 | {
32 | $email = $input->getOption('user');
33 | if (empty($email) || null === $user = $this->manager->getRepository(User::class)->findByEmail($email)) {
34 | throw new UserNotFoundException(sprintf('User with email %s not found!', $email));
35 | }
36 | return $user;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Command/ReportWeeklyCommand.php:
--------------------------------------------------------------------------------
1 | setDescription('Send weekly report to all admins');
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function execute(InputInterface $input, OutputInterface $output): int
35 | {
36 | $this->handler->sendReport();
37 |
38 | return 0;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Command/UsersQuotaCommand.php:
--------------------------------------------------------------------------------
1 | setDescription('Get quota of user if set');
16 | }
17 |
18 | protected function execute(InputInterface $input, OutputInterface $output): int
19 | {
20 | $user = $this->getUser($input);
21 |
22 | // get quota
23 | $quota = $user->getQuota();
24 | if (null === $quota) {
25 | return 0;
26 | }
27 |
28 | $output->writeln(sprintf('%u', $quota));
29 | return 0;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Command/VoucherCountCommand.php:
--------------------------------------------------------------------------------
1 | setDescription('Get count of vouchers for a specific user');
17 | }
18 |
19 | protected function execute(InputInterface $input, OutputInterface $output): int
20 | {
21 | $user = $this->getUser($input);
22 |
23 | $usedCount = $this->manager->getRepository(Voucher::class)->countVouchersByUser($user, true);
24 | $unusedCount = $this->manager->getRepository(Voucher::class)->countVouchersByUser($user, false);
25 | $output->writeln(sprintf("Voucher count for user %s", $user->getEmail()));
26 | $output->writeln(sprintf("Used: %d", $usedCount));
27 | $output->writeln(sprintf("Unused: %d", $unusedCount));
28 |
29 | return 0;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Controller/ErrorController.php:
--------------------------------------------------------------------------------
1 | render('Exception/show.html.twig', [
15 | 'message' => $exception->getMessage(),
16 | 'status_code' => $exception->getStatusCode(),
17 | ]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Controller/RoundcubeController.php:
--------------------------------------------------------------------------------
1 | getUser();
22 |
23 | $aliases = $this->manager->getRepository(Alias::class)->findByUser($user);
24 | $aliasSources = array_map(static function ($alias) { return $alias->getSource(); }, $aliases);
25 | return $this->json($aliasSources);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Controller/SecurityController.php:
--------------------------------------------------------------------------------
1 | getLastAuthenticationError();
23 | // last username entered by the user
24 | $lastUsername = $authenticationUtils->getLastUsername();
25 |
26 | return $this->render('Security/login.html.twig', [
27 | 'last_username' => $lastUsername,
28 | 'error' => $error,
29 | ]);
30 | }
31 |
32 | /**
33 | * @return void
34 | */
35 | #[Route(path: '/logout', name: 'logout', methods: ['GET'])]
36 | public function logout(): void
37 | {
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Creator/AbstractCreator.php:
--------------------------------------------------------------------------------
1 | validator->validate($entity, null, $validationGroups);
30 |
31 | if ($violations->count() > 0) {
32 | throw new ValidationException($violations);
33 | }
34 | }
35 |
36 | /**
37 | * @param $entity
38 | */
39 | protected function save($entity): void
40 | {
41 | $this->manager->persist($entity);
42 | $this->manager->flush();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Creator/AliasCreator.php:
--------------------------------------------------------------------------------
1 | validate($alias, ['Default', 'unique']);
26 | $this->save($alias);
27 |
28 | $this->eventDispatcher->dispatch(new AliasCreatedEvent($alias), AliasCreatedEvent::NAME);
29 | if (null === $localPart) {
30 | $this->eventDispatcher->dispatch(new RandomAliasCreatedEvent($alias), RandomAliasCreatedEvent::NAME);
31 | }
32 |
33 | return $alias;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Creator/DomainCreator.php:
--------------------------------------------------------------------------------
1 | validate($domain, ['Default', 'lowercase', 'unique']);
20 | $this->save($domain);
21 |
22 | $this->eventDispatcher->dispatch(new DomainCreatedEvent($domain), DomainCreatedEvent::NAME);
23 |
24 | return $domain;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Creator/ReservedNameCreator.php:
--------------------------------------------------------------------------------
1 | validate($reservedName);
22 | $this->save($reservedName);
23 |
24 | return $reservedName;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Creator/VoucherCreator.php:
--------------------------------------------------------------------------------
1 | validate($voucher);
23 | $this->save($voucher);
24 |
25 | return $voucher;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/DataFixtures/AbstractUserData.php:
--------------------------------------------------------------------------------
1 | updatePassword($user, self::PASSWORD);
22 | $this->passwordHash = $user->getPassword();
23 | }
24 |
25 | protected function buildUser(Domain $domain, string $email, array $roles): User
26 | {
27 | $user = new User();
28 | $user->setDomain($domain);
29 | $user->setEmail($email);
30 | $user->setRoles($roles);
31 | $user->setPassword($this->passwordHash);
32 |
33 | return $user;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/DataFixtures/LoadAliasData.php:
--------------------------------------------------------------------------------
1 | getRepository(User::class)->findByEmail('user2@example.org');
20 |
21 | $alias = AliasFactory::create($user, 'alias');
22 | $manager->persist($alias);
23 | $alias2 = AliasFactory::create($user, 'alias2');
24 | $manager->persist($alias2);
25 |
26 | $manager->flush();
27 | $manager->clear();
28 | }
29 |
30 | public static function getGroups(): array
31 | {
32 | return ['basic'];
33 | }
34 |
35 | public function getDependencies(): array
36 | {
37 | return [
38 | LoadUserData::class,
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/DataFixtures/LoadDomainData.php:
--------------------------------------------------------------------------------
1 | domains as $name) {
20 | $domain = new Domain();
21 | $domain->setName($name);
22 |
23 | $manager->persist($domain);
24 | }
25 |
26 | $manager->flush();
27 | $manager->clear();
28 | }
29 |
30 | public static function getGroups(): array
31 | {
32 | return ['basic'];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Dto/DovecotPassdbDto.php:
--------------------------------------------------------------------------------
1 | password;
15 | }
16 |
17 | public function setPassword(string $password): void
18 | {
19 | $this->password = $password;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Dto/KeycloakUserValidateDto.php:
--------------------------------------------------------------------------------
1 | password;
16 | }
17 |
18 | public function setPassword(string $password): void {
19 | $this->password = $password;
20 | }
21 |
22 | public function getCredentialType(): string
23 | {
24 | return $this->credentialType;
25 | }
26 |
27 | public function setCredentialType(string $credentialType): void
28 | {
29 | $this->credentialType = $credentialType;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Dto/RetentionTouchUserDto.php:
--------------------------------------------------------------------------------
1 | timestamp;
12 | }
13 |
14 | public function setTimestamp(int $timestamp): void
15 | {
16 | $this->timestamp = $timestamp;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Entity/Domain.php:
--------------------------------------------------------------------------------
1 | creationTime = $currentDateTime;
30 | $this->updatedTime = $currentDateTime;
31 | }
32 |
33 | public function __toString(): string
34 | {
35 | return ($this->getName()) ?: '';
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Entity/Filter/DomainFilter.php:
--------------------------------------------------------------------------------
1 | getDomainId()) {
15 | return '';
16 | }
17 |
18 | // if domain aware
19 | if (array_key_exists('domain', $targetEntity->getAssociationMappings())) {
20 | return sprintf('%s.domain_id = %s', $targetTableAlias, $domainId);
21 | }
22 |
23 | if (Domain::class === $targetEntity->getName()) {
24 | return sprintf('%s.id = %s', $targetTableAlias, $domainId);
25 | }
26 |
27 | return '';
28 | }
29 |
30 | public function getDomainId(): ?string
31 | {
32 | try {
33 | return $this->getParameter('domainId');
34 | } catch (InvalidArgumentException) {
35 | return null;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Entity/OpenPgpKey.php:
--------------------------------------------------------------------------------
1 | getKeyData()) ? base64_decode($this->getKeyData()) : null;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Entity/ReservedName.php:
--------------------------------------------------------------------------------
1 | creationTime = $currentDateTime;
32 | $this->updatedTime = $currentDateTime;
33 | }
34 |
35 | public function __toString(): string
36 | {
37 | return ($this->getName()) ?: '';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Entity/SoftDeletableInterface.php:
--------------------------------------------------------------------------------
1 | self::DISABLED,
16 | '1' => self::ENABLED_OPTIONAL,
17 | '2' => self::ENABLED_ENFORCE_NEW_USERS,
18 | '3' => self::ENABLED_ENFORCE_ALL_USERS,
19 | default => throw new \InvalidArgumentException("Invalid MailCrypt value: $value"),
20 | };
21 | }
22 |
23 | public function isAtLeast(self $other): bool
24 | {
25 | return $this->value >= $other->value;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Enum/Roles.php:
--------------------------------------------------------------------------------
1 | self::PERMANENT,
20 | self::MULTIPLIER => self::MULTIPLIER,
21 | self::SPAM => self::SPAM,
22 | self::SUSPICIOUS => self::SUSPICIOUS,
23 | self::USER => self::USER,
24 | self::DOMAIN_ADMIN => self::DOMAIN_ADMIN,
25 | self::ADMIN => self::ADMIN,
26 | self::KEYCLOAK => self::KEYCLOAK,
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Event/AliasCreatedEvent.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Event/DomainCreatedEvent.php:
--------------------------------------------------------------------------------
1 | domain = $domain;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Event/Events.php:
--------------------------------------------------------------------------------
1 | user = $user;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Event/RandomAliasCreatedEvent.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Event/RecoveryProcessEvent.php:
--------------------------------------------------------------------------------
1 | user = $user;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Event/UserCreatedEvent.php:
--------------------------------------------------------------------------------
1 | user;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Event/UserDeletedEvent.php:
--------------------------------------------------------------------------------
1 | user;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Event/UserEvent.php:
--------------------------------------------------------------------------------
1 | user = $user;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/EventListener/LogoutListener.php:
--------------------------------------------------------------------------------
1 | getRequest()->getSession()->getFlashBag()->add('success', 'flashes.logout-successful');
14 | }
15 |
16 | /**
17 | * {@inheritdoc}
18 | */
19 | public static function getSubscribedEvents(): array
20 | {
21 | return [
22 | LogoutEvent::class => 'onLogoutSuccess',
23 | ];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/EventListener/TwigGlobalListener.php:
--------------------------------------------------------------------------------
1 | manager->getRepository(Domain::class)->getDefaultDomain();
24 | if (null !== $domain) {
25 | $this->twig->addGlobal('domain', $domain->getName());
26 | } else {
27 | $this->twig->addGlobal('domain', 'defaultdomain');
28 | }
29 | }
30 |
31 | public static function getSubscribedEvents(): array
32 | {
33 | return [KernelEvents::CONTROLLER => 'injectGlobalVariables'];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/EventListener/UserCreatedListener.php:
--------------------------------------------------------------------------------
1 | webhookHandler->send($event->getUser(), 'user.created');
19 | }
20 |
21 | public static function getSubscribedEvents(): array
22 | {
23 | return [
24 | UserCreatedEvent::class => 'onUserCreated',
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/EventListener/UserDeletedListener.php:
--------------------------------------------------------------------------------
1 | webhookHandler->send($event->getUser(), 'user.deleted');
19 | }
20 |
21 | public static function getSubscribedEvents(): array
22 | {
23 | return [
24 | UserDeletedEvent::class => 'onUserDeleted',
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Exception/MultipleGpgKeysForUserException.php:
--------------------------------------------------------------------------------
1 | getMessage();
20 |
21 | if (!empty($constraint->getPropertyPath()) && is_string($constraint->getInvalidValue())) {
22 | $message = sprintf('%s [%s => %s]', $message, $constraint->getPropertyPath(), $constraint->getInvalidValue());
23 | }
24 |
25 | $messages[] = $message;
26 | }
27 | $message = implode(PHP_EOL, $messages);
28 |
29 | parent::__construct($message);
30 | }
31 |
32 | public function getConstraints(): ConstraintViolationListInterface
33 | {
34 | return $this->constraints;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Factory/AliasFactory.php:
--------------------------------------------------------------------------------
1 | getDomain();
23 | $alias = new Alias();
24 | $alias->setUser($user);
25 | $alias->setDomain($domain);
26 | $alias->setDestination($user->getEmail());
27 | if (null === $localPart) {
28 | $localPart = RandomStringGenerator::generate(self::RANDOM_ALIAS_LENGTH, false);
29 | $alias->setRandom(true);
30 | }
31 |
32 | $alias->setSource($localPart.'@'.$domain->getName());
33 |
34 | return $alias;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Factory/DomainFactory.php:
--------------------------------------------------------------------------------
1 | setName($name);
13 |
14 | return $domain;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Factory/ReservedNameFactory.php:
--------------------------------------------------------------------------------
1 | setName($name);
16 |
17 | return $reservedName;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Factory/VoucherFactory.php:
--------------------------------------------------------------------------------
1 | setUser($user);
22 | $voucher->setCode(RandomStringGenerator::generate(6, true));
23 |
24 | return $voucher;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Form/AliasDeleteType.php:
--------------------------------------------------------------------------------
1 | add('password', PasswordType::class, ['label' => 'form.delete-password'])
19 | ->add('submit', SubmitType::class, ['label' => 'form.delete-alias']);
20 | }
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function configureOptions(OptionsResolver $resolver)
26 | {
27 | $resolver->setDefaults(['data_class' => 'App\Form\Model\Delete']);
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getBlockPrefix()
34 | {
35 | return self::NAME;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Form/CustomAliasCreateType.php:
--------------------------------------------------------------------------------
1 | add('alias', TextType::class, ['label' => 'form.new-custom-alias'])
19 | ->add('submit', SubmitType::class, ['label' => 'form.create-custom-alias']);
20 | }
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function configureOptions(OptionsResolver $resolver)
26 | {
27 | $resolver->setDefaults(['data_class' => 'App\Form\Model\AliasCreate']);
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getBlockPrefix()
34 | {
35 | return self::NAME;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Form/DataTransformer/OptionalDomainEmailTransformer.php:
--------------------------------------------------------------------------------
1 | domain);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Form/DataTransformer/TextToEmailTransformer.php:
--------------------------------------------------------------------------------
1 | domain);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Form/DomainCreateType.php:
--------------------------------------------------------------------------------
1 | add('domain', TextType::class, ['label' => 'form.domain'])
19 | ->add('submit', SubmitType::class, ['label' => 'form.add']);
20 | }
21 |
22 | public function configureOptions(OptionsResolver $resolver)
23 | {
24 | $resolver->setDefaults(['data_class' => 'App\Form\Model\DomainCreate']);
25 | }
26 |
27 | /**
28 | * @return string
29 | */
30 | public function getBlockPrefix()
31 | {
32 | return self::NAME;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Form/Model/AliasCreate.php:
--------------------------------------------------------------------------------
1 | keyFile;
16 | }
17 |
18 | /**
19 | * @return $this
20 | */
21 | public function setKeyFile(string $keyFile): self
22 | {
23 | $this->keyFile = $keyFile;
24 |
25 | return $this;
26 | }
27 |
28 | public function getKeyText(): ?string
29 | {
30 | return $this->keyText;
31 | }
32 |
33 | /**
34 | * @return $this
35 | */
36 | public function setKeyText(string $keyText): self
37 | {
38 | $this->keyText = $keyText;
39 |
40 | return $this;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Form/Model/PasswordChange.php:
--------------------------------------------------------------------------------
1 | password;
22 | }
23 |
24 | public function setPassword(string $password): void
25 | {
26 | $this->password = $password;
27 | }
28 |
29 | public function getNewPassword(): string
30 | {
31 | return $this->newPassword;
32 | }
33 |
34 | public function setNewPassword(string $newPassword): void
35 | {
36 | $this->newPassword = $newPassword;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Form/Model/PlainPassword.php:
--------------------------------------------------------------------------------
1 | voucher;
26 | }
27 |
28 | public function setVoucher(string $voucher): void
29 | {
30 | $this->voucher = $voucher;
31 | }
32 |
33 | public function getEmail(): ?string
34 | {
35 | return $this->email;
36 | }
37 |
38 | public function setEmail(string $email): void
39 | {
40 | $this->email = strtolower($email);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Form/Model/Twofactor.php:
--------------------------------------------------------------------------------
1 | add('password', PasswordType::class, ['label' => 'form.delete-password'])
19 | ->add('submit', SubmitType::class, ['label' => 'form.openpgp-delete']);
20 | }
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function configureOptions(OptionsResolver $resolver)
26 | {
27 | $resolver->setDefaults(['data_class' => 'App\Form\Model\Delete']);
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getBlockPrefix()
34 | {
35 | return self::NAME;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Form/RandomAliasCreateType.php:
--------------------------------------------------------------------------------
1 | add('submit', SubmitType::class, ['label' => 'form.create-random-alias']);
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function configureOptions(OptionsResolver $resolver): void
25 | {
26 | $resolver->setDefaults(['data_class' => AliasCreate::class]);
27 | }
28 |
29 | public function getBlockPrefix(): string
30 | {
31 | return self::NAME;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Form/RecoveryTokenType.php:
--------------------------------------------------------------------------------
1 | add('password', PasswordType::class, ['label' => 'form.password'])
20 | ->add('submit', SubmitType::class, ['label' => 'form.generate-recovery-token']);
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function configureOptions(OptionsResolver $resolver): void
27 | {
28 | $resolver->setDefaults(['data_class' => RecoveryToken::class]);
29 | }
30 |
31 | public function getBlockPrefix(): string
32 | {
33 | return self::NAME;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Form/TwofactorBackupAckType.php:
--------------------------------------------------------------------------------
1 | add('ack', CheckboxType::class, [
20 | 'required' => true,
21 | 'label' => 'form.twofactor-backup-code-ack',
22 | ])
23 | ->add('submit', SubmitType::class, ['label' => 'form.verify']);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function configureOptions(OptionsResolver $resolver): void
30 | {
31 | $resolver->setDefaults(['data_class' => TwofactorBackupAck::class]);
32 | }
33 |
34 | public function getBlockPrefix(): string
35 | {
36 | return self::NAME;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Form/TwofactorConfirmType.php:
--------------------------------------------------------------------------------
1 | add('totpSecret', TextType::class, [
20 | 'required' => true,
21 | 'label' => 'form.twofactor-login-auth-code',
22 | ])
23 | ->add('submit', SubmitType::class, ['label' => 'form.verify']);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function configureOptions(OptionsResolver $resolver): void
30 | {
31 | $resolver->setDefaults(['data_class' => TwofactorConfirm::class]);
32 | }
33 |
34 | public function getBlockPrefix(): string
35 | {
36 | return self::NAME;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Form/TwofactorType.php:
--------------------------------------------------------------------------------
1 | add('password', PasswordType::class, ['label' => 'form.password'])
20 | ->add('submit', SubmitType::class, ['label' => 'form.twofactor-enable']);
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function configureOptions(OptionsResolver $resolver): void
27 | {
28 | $resolver->setDefaults(['data_class' => Twofactor::class]);
29 | }
30 |
31 | public function getBlockPrefix(): string
32 | {
33 | return self::NAME;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Form/UserDeleteType.php:
--------------------------------------------------------------------------------
1 | add('password', PasswordType::class, ['label' => 'form.delete-password'])
20 | ->add('submit', SubmitType::class, ['label' => 'form.delete-account']);
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function configureOptions(OptionsResolver $resolver): void
27 | {
28 | $resolver->setDefaults(['data_class' => Delete::class]);
29 | }
30 |
31 | public function getBlockPrefix(): string
32 | {
33 | return self::NAME;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Form/VoucherCreateType.php:
--------------------------------------------------------------------------------
1 | add('submit', SubmitType::class, ['label' => 'form.create-voucher']);
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function configureOptions(OptionsResolver $resolver): void
25 | {
26 | $resolver->setDefaults(['data_class' => VoucherCreate::class]);
27 | }
28 |
29 | public function getBlockPrefix(): string
30 | {
31 | return self::NAME;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Guesser/DomainGuesser.php:
--------------------------------------------------------------------------------
1 | repository = $manager->getRepository(Domain::class);
19 | }
20 |
21 | public function guess(string $email): ?Domain
22 | {
23 | $splitted = explode('@', $email);
24 |
25 | return isset($splitted[1]) ? $this->repository->findByName($splitted[1]) : null;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Handler/MailHandler.php:
--------------------------------------------------------------------------------
1 | from(new Address($this->from, $this->name))
19 | ->to($email)
20 | ->subject($subject)
21 | ->text($plain);
22 |
23 | if (isset($params['bcc'])) {
24 | $message->bcc($params['bcc']);
25 | }
26 |
27 | if (isset($params['html'])) {
28 | $message->html($params['html']);
29 | }
30 |
31 | $this->mailer->send($message);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Handler/PasswordStrengthHandler.php:
--------------------------------------------------------------------------------
1 | errors[] = 'form.forbidden_char';
20 | }
21 |
22 | if (strlen((string) $value) < 12) {
23 | $this->errors[] = 'form.weak_password';
24 | }
25 |
26 | return $this->errors;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Handler/SuspiciousChildrenHandler.php:
--------------------------------------------------------------------------------
1 | twig->render('Email/suspicious_children.twig', ['suspiciousChildren' => $suspiciousChildren]);
29 | $this->handler->send($this->to, $message, 'Suspicious users invited more users');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Handler/UserAuthenticationHandler.php:
--------------------------------------------------------------------------------
1 | passwordHasherFactory->getPasswordHasher($user);
25 | if (!$hasher->verify($user->getPassword(), $password)) {
26 | return null;
27 | }
28 | $this->eventDispatcher->dispatch(new LoginEvent($user), LoginEvent::NAME);
29 |
30 | return $user;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Handler/UserRegistrationInfoHandler.php:
--------------------------------------------------------------------------------
1 | manager->getRepository(User::class)->findUsersSince((new DateTime())->modify($from));
33 | $message = $this->twig->render('Email/weekly_report.twig', ['users' => $users]);
34 | $this->handler->send($this->to, $message, 'Weekly Report: Registered E-mail Accounts');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helper/AdminPasswordUpdater.php:
--------------------------------------------------------------------------------
1 | manager->getRepository(Domain::class)->getDefaultDomain();
23 | $adminEmail = 'postmaster@'.$domain;
24 | $admin = $this->manager->getRepository(User::class)->findByEmail($adminEmail);
25 | if (null === $admin) {
26 | // create admin user
27 | $admin = new User();
28 | $admin->setEmail($adminEmail);
29 | $admin->setRoles([Roles::ADMIN]);
30 | $admin->setDomain($domain);
31 | }
32 | $this->updater->updatePassword($admin, $password);
33 | $this->manager->persist($admin);
34 | $this->manager->flush();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helper/PasswordGenerator.php:
--------------------------------------------------------------------------------
1 | setPassword($this->passwordHasherFactory->getPasswordHasher($user)->hash($plainPassword));
17 | $user->updateUpdatedTime();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Helper/RandomStringGenerator.php:
--------------------------------------------------------------------------------
1 | environment, ['dev', 'test'])) {
18 | $projectDir = '/dev/shm/userli';
19 | }
20 |
21 | return $projectDir.'/var/cache/'.$this->environment;
22 | }
23 |
24 | public function getLogDir(): string
25 | {
26 | $projectDir = parent::getProjectDir();
27 | if ('/vagrant' === $projectDir && in_array($this->environment, ['dev', 'test'])) {
28 | $projectDir = '/dev/shm/userli';
29 | }
30 |
31 | return $projectDir.'/var/log';
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Model/MailCryptKeyPair.php:
--------------------------------------------------------------------------------
1 | privateKey = $privateKey;
20 | $this->publicKey = $publicKey;
21 | }
22 |
23 | /**
24 | * @throws SodiumException
25 | */
26 | public function erase(): void
27 | {
28 | sodium_memzero($this->privateKey);
29 | sodium_memzero($this->publicKey);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Remover/VoucherRemover.php:
--------------------------------------------------------------------------------
1 | removeUnredeemedVouchersByUsers([$user]);
25 | }
26 |
27 | public function removeUnredeemedVouchersByUsers(array $users): void
28 | {
29 | $criteria = Criteria::create()
30 | ->where(Criteria::expr()->isNull('redeemedTime'))
31 | ->andWhere(Criteria::expr()->in('user', $users));
32 |
33 | $this->manager->getRepository(Voucher::class)
34 | ->createQueryBuilder('a')
35 | ->addCriteria($criteria)
36 | ->delete()
37 | ->getQuery()
38 | ->execute();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Repository/DomainRepository.php:
--------------------------------------------------------------------------------
1 | findOneBy(['name' => $name]);
13 | }
14 |
15 | public function getDefaultDomain(): ?Domain
16 | {
17 | return $this->findOneBy([], ['id' => 'ASC']);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Repository/OpenPgpKeyRepository.php:
--------------------------------------------------------------------------------
1 | findBy(['user' => $user]);
17 | }
18 |
19 | public function findByEmail(string $email): ?OpenPgpKey
20 | {
21 | return $this->findOneBy(['email' => $email]);
22 | }
23 |
24 | public function countKeys(): int
25 | {
26 | return $this->count([]);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Repository/ReservedNameRepository.php:
--------------------------------------------------------------------------------
1 | findOneBy(['name' => $name]);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Security/ApiAccessTokenHandler.php:
--------------------------------------------------------------------------------
1 | accessTokenDovecot:
22 | return new UserBadge('dovecot');
23 | case $this->accessTokenKeycloak:
24 | return new UserBadge('keycloak');
25 | case $this->accessTokenRetention:
26 | return new UserBadge('retention');
27 | case $this->accessTokenPostfix:
28 | return new UserBadge('postfix');
29 | default:
30 | throw new BadCredentialsException('Invalid access token');
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Security/UserChecker.php:
--------------------------------------------------------------------------------
1 | isDeleted()) {
19 | throw new CustomUserMessageAccountStatusException('Bad credentials.');
20 | }
21 | }
22 |
23 | public function checkPostAuth(UserInterface $user): void
24 | {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Sender/AliasCreatedMessageSender.php:
--------------------------------------------------------------------------------
1 | getEmail()) {
29 | throw new Exception('Email should not be null');
30 | }
31 |
32 | $body = $this->builder->buildBody($locale, $email, $alias->getSource());
33 | $subject = $this->builder->buildSubject($locale, $email);
34 | $this->handler->send($email, $body, $subject);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Sender/RecoveryProcessMessageSender.php:
--------------------------------------------------------------------------------
1 | getEmail()) {
30 | throw new Exception('Email should not be null');
31 | }
32 |
33 | $formatter = IntlDateFormatter::create($locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
34 | $time = $formatter->format($user->getRecoveryStartTime()->add(new DateInterval('P2D')));
35 |
36 | $body = $this->builder->buildBody($locale, $email, $time);
37 | $subject = $this->builder->buildSubject($locale, $email);
38 | $this->handler->send($email, $body, $subject);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Sender/WelcomeMessageSender.php:
--------------------------------------------------------------------------------
1 | getRepository(Domain::class)->getDefaultDomain();
25 | $this->domain = null !== $domain ? $domain->getName() : '';
26 | }
27 |
28 | /**
29 | * @throws Exception
30 | */
31 | public function send(User $user, string $locale): void
32 | {
33 | if (null === $email = $user->getEmail()) {
34 | throw new Exception('Email should not be null');
35 | }
36 |
37 | if (strpos($email, (string) $this->domain) < 0) {
38 | return;
39 | }
40 |
41 | $body = $this->builder->buildBody($locale);
42 | $subject = $this->builder->buildSubject($locale);
43 | $this->handler->send($email, $body, $subject);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Traits/AliasAwareTrait.php:
--------------------------------------------------------------------------------
1 | alias;
14 | }
15 |
16 | public function setAlias(Alias $alias): void
17 | {
18 | $this->alias = $alias;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Traits/CreationTimeTrait.php:
--------------------------------------------------------------------------------
1 | creationTime;
16 | }
17 |
18 | public function setCreationTime(DateTime $creationTime): void
19 | {
20 | $this->creationTime = $creationTime;
21 | }
22 |
23 | #[ORM\PrePersist]
24 | public function updateCreationTime(): void
25 | {
26 | if (null === $this->creationTime) {
27 | $this->setCreationTime(new DateTime());
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Traits/DeleteTrait.php:
--------------------------------------------------------------------------------
1 | false])]
10 | private bool $deleted = false;
11 |
12 | public function isDeleted(): bool
13 | {
14 | return $this->deleted;
15 | }
16 |
17 | public function getDeleted(): bool
18 | {
19 | return $this->deleted;
20 | }
21 |
22 | public function setDeleted(bool $deleted): void
23 | {
24 | $this->deleted = $deleted;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Traits/DomainAwareTrait.php:
--------------------------------------------------------------------------------
1 | domain;
19 | }
20 |
21 | public function setDomain(Domain $domain): void
22 | {
23 | $this->domain = $domain;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Traits/DomainGuesserAwareTrait.php:
--------------------------------------------------------------------------------
1 | domainGuesser;
14 | }
15 |
16 | public function setDomainGuesser(DomainGuesser $domainGuesser): void
17 | {
18 | $this->domainGuesser = $domainGuesser;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Traits/EmailTrait.php:
--------------------------------------------------------------------------------
1 | email;
21 | }
22 |
23 | public function setEmail(string $email): void
24 | {
25 | $this->email = $email;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Traits/IdTrait.php:
--------------------------------------------------------------------------------
1 | id;
17 | }
18 |
19 | public function setId(int $id): void
20 | {
21 | $this->id = $id;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Traits/InvitationVoucherTrait.php:
--------------------------------------------------------------------------------
1 | invitationVoucher;
19 | }
20 |
21 | public function setInvitationVoucher(Voucher $invitationVoucher = null): void
22 | {
23 | $this->invitationVoucher = $invitationVoucher;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Traits/LastLoginTimeTrait.php:
--------------------------------------------------------------------------------
1 | lastLoginTime;
16 | }
17 |
18 | public function setLastLoginTime(?DateTime $LastLoginTime): void
19 | {
20 | $this->lastLoginTime = $LastLoginTime;
21 | }
22 |
23 | public function updateLastLoginTime(): void
24 | {
25 | $this->setLastLoginTime(new DateTime());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Traits/MailCryptEnabledTrait.php:
--------------------------------------------------------------------------------
1 | false], name: "mail_crypt")]
10 | private bool $mailCryptEnabled = false;
11 |
12 | public function getMailCryptEnabled(): bool
13 | {
14 | return $this->mailCryptEnabled;
15 | }
16 |
17 | public function setMailCryptEnabled(bool $mailCrypt): void
18 | {
19 | $this->mailCryptEnabled = $mailCrypt;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Traits/MailCryptPublicKeyTrait.php:
--------------------------------------------------------------------------------
1 | mailCryptPublicKey;
15 | }
16 |
17 | public function setMailCryptPublicKey(string $mailCryptPublicKey): void
18 | {
19 | $this->mailCryptPublicKey = $mailCryptPublicKey;
20 | }
21 |
22 | public function hasMailCryptPublicKey(): bool
23 | {
24 | return (bool) $this->getMailCryptPublicKey();
25 | }
26 |
27 | public function eraseMailCryptPublicKey(): void
28 | {
29 | $this->mailCryptPublicKey = null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Traits/MailCryptSecretBoxTrait.php:
--------------------------------------------------------------------------------
1 | mailCryptSecretBox;
15 | }
16 |
17 | public function setMailCryptSecretBox(?string $mailCryptSecretBox): void
18 | {
19 | $this->mailCryptSecretBox = $mailCryptSecretBox;
20 | }
21 |
22 | public function hasMailCryptSecretBox(): bool
23 | {
24 | return (bool) $this->getMailCryptSecretBox();
25 | }
26 |
27 | public function eraseMailCryptSecretBox(): void
28 | {
29 | $this->mailCryptSecretBox = null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Traits/NameTrait.php:
--------------------------------------------------------------------------------
1 | name;
20 | }
21 |
22 | public function setName(?string $name): void
23 | {
24 | $this->name = $name;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Traits/PasswordTrait.php:
--------------------------------------------------------------------------------
1 | password;
15 | }
16 |
17 | public function setPassword(?string $password): void
18 | {
19 | $this->password = $password;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Traits/PasswordVersionTrait.php:
--------------------------------------------------------------------------------
1 | passwordVersion;
15 | }
16 |
17 | public function setPasswordVersion(?int $passwordVersion): void
18 | {
19 | $this->passwordVersion = $passwordVersion;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Traits/PlainMailCryptPrivateKeyTrait.php:
--------------------------------------------------------------------------------
1 | plainMailCryptPrivateKey;
15 | }
16 |
17 | public function setPlainMailCryptPrivateKey(?string $plainMailCryptPrivateKey): void
18 | {
19 | $this->plainMailCryptPrivateKey = $plainMailCryptPrivateKey;
20 | }
21 |
22 | public function erasePlainMailCryptPrivateKey(): void
23 | {
24 | $this->plainMailCryptPrivateKey = null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Traits/PlainPasswordTrait.php:
--------------------------------------------------------------------------------
1 | plainPassword;
20 | }
21 |
22 | public function setPlainPassword(?string $plainPassword): void
23 | {
24 | $this->plainPassword = $plainPassword;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Traits/PlainRecoveryTokenTrait.php:
--------------------------------------------------------------------------------
1 | plainRecoveryToken;
15 | }
16 |
17 | public function setPlainRecoveryToken(?string $plainRecoveryToken): void
18 | {
19 | $this->plainRecoveryToken = $plainRecoveryToken;
20 | }
21 |
22 | public function erasePlainRecoveryToken(): void
23 | {
24 | $this->plainRecoveryToken = null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Traits/PrivateKeyTrait.php:
--------------------------------------------------------------------------------
1 | privateKey;
12 | }
13 |
14 | public function setPrivateKey(?string $privateKey): void
15 | {
16 | $this->privateKey = $privateKey;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Traits/PublicKeyTrait.php:
--------------------------------------------------------------------------------
1 | publicKey;
12 | }
13 |
14 | public function setPublicKey(?string $publicKey): void
15 | {
16 | $this->publicKey = $publicKey;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Traits/QuotaTrait.php:
--------------------------------------------------------------------------------
1 | quota;
15 | }
16 |
17 | public function setQuota(?int $quota): void
18 | {
19 | $this->quota = $quota;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Traits/RandomTrait.php:
--------------------------------------------------------------------------------
1 | false])]
10 | private bool $random = false;
11 |
12 | public function isRandom(): bool
13 | {
14 | return $this->random;
15 | }
16 |
17 | public function setRandom(bool $random): void
18 | {
19 | $this->random = $random;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Traits/RecoverySecretBoxTrait.php:
--------------------------------------------------------------------------------
1 | recoverySecretBox;
15 | }
16 |
17 | public function setRecoverySecretBox(?string $recoverySecretBox): void
18 | {
19 | $this->recoverySecretBox = $recoverySecretBox;
20 | }
21 |
22 | public function hasRecoverySecretBox(): bool
23 | {
24 | return (bool) $this->getRecoverySecretBox();
25 | }
26 |
27 | public function eraseRecoverySecretBox(): void
28 | {
29 | $this->recoverySecretBox = null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Traits/RecoveryStartTimeTrait.php:
--------------------------------------------------------------------------------
1 | recoveryStartTime;
17 | }
18 |
19 | public function setRecoveryStartTime(DateTime $recoveryStartTime): void
20 | {
21 | $this->recoveryStartTime = $recoveryStartTime;
22 | }
23 |
24 | /**
25 | * @throws Exception
26 | */
27 | public function updateRecoveryStartTime(): void
28 | {
29 | $this->setRecoveryStartTime(new DateTime());
30 | }
31 |
32 | public function eraseRecoveryStartTime(): void
33 | {
34 | $this->recoveryStartTime = null;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Traits/RecoveryTokenTrait.php:
--------------------------------------------------------------------------------
1 | recoveryToken;
12 | }
13 |
14 | public function setRecoveryToken(?string $recoveryToken): void
15 | {
16 | $this->recoveryToken = $recoveryToken;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Traits/SaltTrait.php:
--------------------------------------------------------------------------------
1 | salt;
12 | }
13 |
14 | public function setSalt(?string $salt): void
15 | {
16 | $this->salt = $salt;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Traits/UpdatedTimeTrait.php:
--------------------------------------------------------------------------------
1 | updatedTime;
16 | }
17 |
18 | public function setUpdatedTime(DateTime $updatedTime): void
19 | {
20 | $this->updatedTime = $updatedTime;
21 | }
22 |
23 | #[ORM\PrePersist]
24 | public function updateUpdatedTime(): void
25 | {
26 | $this->setUpdatedTime(new DateTime());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Traits/UserAwareTrait.php:
--------------------------------------------------------------------------------
1 | user;
16 | }
17 |
18 | public function setUser(User $user): void
19 | {
20 | $this->user = $user;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/EmailAddress.php:
--------------------------------------------------------------------------------
1 | getEmail(), '@'), 1);
24 | $domain = $this->manager->getRepository(Domain::class)->findOneBy(['name' => $name]);
25 |
26 | if (null === $domain) {
27 | $this->context->addViolation('form.missing-domain');
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/EmailLength.php:
--------------------------------------------------------------------------------
1 | minLength = $minLength ?? $this->minLength;
25 | $this->maxLength = $maxLength ?? $this->maxLength;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/EmailLengthValidator.php:
--------------------------------------------------------------------------------
1 | minLength) && strlen($localPart) < $minLength) {
28 | $this->context->addViolation('registration.email-too-short', ['%min%' => $constraint->minLength]);
29 | }
30 |
31 | if (is_numeric($maxLength = $constraint->maxLength) && strlen($localPart) > $maxLength) {
32 | $this->context->addViolation('registration.email-too-long', ['%max%' => $constraint->maxLength]);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/Lowercase.php:
--------------------------------------------------------------------------------
1 | mode = $mode ?? $this->mode;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/LowercaseValidator.php:
--------------------------------------------------------------------------------
1 | context->buildViolation($constraint->message)
33 | ->setParameter('{{ string }}', $value)
34 | ->addViolation();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/PasswordPolicy.php:
--------------------------------------------------------------------------------
1 | exists = $exists ?? $this->exists;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/VoucherUser.php:
--------------------------------------------------------------------------------
1 | getUser();
25 | if (null !== $user && $user->hasRole(Roles::SUSPICIOUS)) {
26 | $this->context->addViolation('voucher.suspicious-user');
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Validator/PasswordPolicyValidator.php:
--------------------------------------------------------------------------------
1 | handler->validate($value);
22 |
23 | if (!empty($errors)) {
24 | foreach ($errors as $error) {
25 | $this->context->addViolation($error);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/templates/Admin/standard_layout.html.twig:
--------------------------------------------------------------------------------
1 | {% extends '@SonataAdmin/standard_layout.html.twig' %}
2 |
3 | {% block sonata_sidebar_search %}
4 | {% endblock %}
5 |
6 | {% block side_bar_after_nav %}
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/templates/Admin/user_block.html.twig:
--------------------------------------------------------------------------------
1 | {% block user_block %}
2 |
3 |
4 | Return to Index
5 |
6 |
7 | Logout
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/templates/Block/block_statistics.html.twig:
--------------------------------------------------------------------------------
1 | {% extends sonata_block.templates.block_base %}
2 |
3 | {% block block %}
4 |
5 |
6 |
9 |
10 |
11 | - {{ "admin.registered-accounts"|trans }}
12 | - {{ users_count }}
13 | - {{ "admin.registered-accounts-last-week"|trans }}s
14 | - {{ users_since }}
15 | {% if is_granted('ROLE_ADMIN') %}
16 | - {{ "admin.invite-codes"|trans }}
17 | - {{ vouchers_count }}
18 | - {{ "admin.invite-codes-redeemed"|trans }}
19 | - {{ vouchers_redeemed }} ({{ "Ratio"|trans }}: {{ vouchers_ratio }})
20 | {% endif %}
21 |
22 |
23 |
24 |
25 | {% endblock %}
26 |
--------------------------------------------------------------------------------
/templates/Email/suspicious_children.twig:
--------------------------------------------------------------------------------
1 | {% for child, parent in suspiciousChildren %}
2 | Suspicious User {{ parent }} has invited {{ child }}.
3 | {% endfor %}
--------------------------------------------------------------------------------
/templates/Email/weekly_report.twig:
--------------------------------------------------------------------------------
1 | {{ users|length }} registrations since last week
2 |
3 | ┌────────────────────────────────┬─────────────────────┐
4 | │ {{ '%-30.30s'|format('Account') }} │ {{ '%-19.19s'|format('Date') }} │
5 | ├────────────────────────────────┼─────────────────────┤
6 | {% for user in users %}
7 | │ {{ '%-30.30s'|format(user.email) }} │ {{ user.creationTime|date('d.m.Y H:i:s') }} │
8 | {% endfor %}
9 | └────────────────────────────────┴─────────────────────┘
10 |
11 |
--------------------------------------------------------------------------------
/templates/Exception/show.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "error.title"|trans }}{% endblock %}
4 |
5 | {% block navbar %}
6 | {% embed '@MopaBootstrap/Navbar/navbar.html.twig' with { fixedTop: true, staticTop: true, inverse: true, fluid: true } %}
7 | {% block brand %}
8 | {{ domain }}
9 | {% endblock %}
10 |
11 | {% block menu %}
12 | {{ mopa_bootstrap_menu('navbar-left') }}
13 | {% endblock %}
14 | {% endembed %}
15 | {% endblock %}
16 |
17 | {% block content %}
18 |
19 |
20 |
{{ "error.title"|trans }}
21 | {% if status_code == 404 %}
22 |
{{ "error.page_not_found"|trans }}
23 | {% else %}
24 |
{{ "error.generic_error"|trans }}
25 | {% endif %}
26 |
{{ "error.back_link"|trans }}
27 |
28 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/templates/Footer/logged_in_footer.twig:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/templates/Form/fields.html.twig:
--------------------------------------------------------------------------------
1 | {% block form_errors %}
2 | {% apply spaceless %}
3 | {% if errors|length > 0 %}
4 |
5 | {% for error in errors %}
6 |
{{ error.message }}
7 | {% endfor %}
8 |
9 | {% endif %}
10 | {% endapply %}
11 | {% endblock form_errors %}
12 |
--------------------------------------------------------------------------------
/templates/Init/domain.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% form_theme form 'Form/fields.html.twig' %}
4 |
5 | {% block title %}{{ "init.title"|trans }}{% endblock %}
6 |
7 | {% block subtitle %}{{ "init.title"|trans }}{% endblock %}
8 |
9 | {% block content %}
10 |
11 |
12 |
13 |
{{ "init.title"|trans }}
14 |
15 |
{{ "init_domain.lead"|trans }}
16 |
{{ "init_domain.text"|trans }}
17 |
18 |
19 |
20 | {{ form_start(form) }}
21 | {{ form_errors(form) }}
22 |
23 |
24 | {{ form_label(form.domain) }}
25 | {{ form_errors(form.domain) }}
26 | {{ form_widget(form.domain, {'attr': {'class': 'form-control' }}) }}
27 |
28 |
29 |
30 | {{ form_widget(form.submit, {'attr': {'class': 'btn btn-primary' }}) }}
31 |
32 | {{ form_end(form) }}
33 |
34 |
35 |
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/templates/Recovery/new_recovery_token.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "recovery.header"|trans }}{% endblock %}
4 |
5 | {% block content %}
6 |
7 |
8 |
{{ "recovery.header"|trans }}
9 |
{{ "recovery.token-lead"|trans }}
10 |
11 |
12 |
13 |
14 | {% include 'Recovery/show_recovery_token.html.twig' %}
15 |
16 |
17 | {% include 'Recovery/recovery_token_notes.html.twig' %}
18 |
19 |
20 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/templates/Recovery/recovery.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "recovery.header"|trans }}{% endblock %}
4 |
5 | {% form_theme form 'Form/fields.html.twig' %}
6 |
7 | {% block content %}
8 |
9 |
10 |
{{ "recovery.header"|trans }}
11 |
{{ "recovery.lead"|trans }}
12 | {% block recovery_content %}
13 | {% endblock %}
14 |
15 |
16 |
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/templates/Recovery/recovery_new.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Recovery/recovery.html.twig' %}
2 |
3 | {% block recovery_content %}
4 | {{ form_start(form) }}
5 | {{ form_errors(form) }}
6 |
7 | {{ form_label(form.email) }}
8 | {{ form_errors(form.email) }}
9 | {{ form_widget(form.email, {'attr': {'class': 'form-control' }}) }}
10 |
11 |
12 |
13 | {{ form_label(form.recoveryToken) }}
14 | {{ form_errors(form.recoveryToken) }}
15 | {{ form_widget(form.recoveryToken, {'attr': {'class': 'form-control', 'placeholder': 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' }}) }}
16 |
17 |
18 |
22 |
23 | {{ form_end(form) }}
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/templates/Recovery/recovery_started.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Recovery/recovery.html.twig' %}
2 |
3 | {% block recovery_content %}
4 | {{ "recovery.started"|trans }}
{{ "recovery.waiting-info"|trans }}
5 | {{ "recovery.waiting-time"|trans({'%time%': active_time|date("d.m.Y")}) }}
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/templates/Recovery/recovery_token.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "recovery.token-lead"|trans }}{% endblock %}
4 |
5 | {% form_theme form 'Form/fields.html.twig' %}
6 |
7 | {% block content %}
8 |
9 |
10 |
{{ "recovery.token-lead"|trans }}
11 |
12 |
13 |
14 |
15 | {% include 'Recovery/show_recovery_token.html.twig' %}
16 |
17 |
18 | {% include 'Recovery/recovery_token_notes.html.twig' %}
19 |
20 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/templates/Recovery/recovery_token_notes.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 | - {{ "recovery-token.created-info"|trans }}
4 |
5 | - {{ "recovery-token.displayed-once"|trans }}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/templates/Recovery/show_recovery_token.html.twig:
--------------------------------------------------------------------------------
1 | {{ "recovery-token.created-headline"|trans }}
2 | {{ recovery_token }}
3 |
4 | {{ form_start(form) }}
5 | {{ form_errors(form) }}
6 | {{ form_widget(form.recoveryToken) }}
7 |
8 | {{ form_errors(form.ack) }}
9 | {{ form_widget(form.ack) }}
10 | {{ form_label(form.ack) }}
11 |
12 |
13 | {{ form_widget(form.submit, {'attr': {'class': 'btn btn-primary' } }) }}
14 |
15 | {{ form_end(form) }}
16 |
--------------------------------------------------------------------------------
/templates/Registration/closed.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "closed.title"|trans }}{% endblock %}
4 |
5 | {% block content %}
6 |
7 |
8 |
{{ "closed.headline"|trans }}
9 |
10 |
{{ "closed.lead"|trans }}
11 |
12 | {{ "closed.text"|trans|raw }}
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/Registration/recovery_token.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "registration.recovery-token-headline"|trans }}{% endblock %}
4 |
5 | {% form_theme form 'Form/fields.html.twig' %}
6 |
7 | {% block content %}
8 |
9 |
10 |
{{ "recovery-token.headline"|trans }}
11 |
12 |
13 |
14 |
15 | {% include 'Recovery/show_recovery_token.html.twig' %}
16 |
17 |
18 | {% include 'Recovery/recovery_token_notes.html.twig' %}
19 |
20 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/templates/Registration/welcome.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "welcome.headline"|trans({'%domain%': domain, '%project_name%': project_name}) }}{% endblock %}
4 |
5 | {% block content %}
6 |
7 |
8 |
9 |
{{ "welcome.headline"|trans({'%domain%': domain, '%project_name%': project_name}) }}
10 |
11 |
{{ "welcome.lead"|trans }}
12 |
13 | {{ "welcome.text"|trans({'%project_name%': project_name, '%app_url%': app_url})|raw }}
14 |
15 |
{{ "welcome.next-button"|trans }}
16 |
17 |
18 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/templates/Start/account.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "start.account-settings"|trans }}{% endblock %}
4 |
5 | {% block breadcrumbs %}
6 |
7 | - Start
8 | - {{ "start.account-settings"|trans }}
9 |
10 | {% endblock %}
11 | {% block content %}
12 |
13 |
14 | {% include 'Start/change_password.html.twig' %}
15 |
16 |
17 | {% include 'Start/twofactor.html.twig' %}
18 |
19 |
20 | {% include 'Start/recovery_token.html.twig' %}
21 |
22 |
23 | {% include 'Start/delete_account.html.twig' %}
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/templates/Start/change_password.html.twig:
--------------------------------------------------------------------------------
1 | {% form_theme password_form 'Form/fields.html.twig' %}
2 |
3 | {{ form_start(password_form) }}
4 | {{ "form.change-password"|trans }}
5 |
6 | {{ form_errors(password_form) }}
7 |
8 |
9 | {{ form_label(password_form.password) }}
10 | {{ form_errors(password_form.password) }}
11 | {{ form_widget(password_form.password, {'attr': {'class': 'form-control' }}) }}
12 |
13 |
14 |
15 | {{ form_label(password_form.newPassword.first) }}
16 | {{ form_errors(password_form.newPassword.first) }}
17 | {{ form_widget(password_form.newPassword.first, {'attr': {'class': 'form-control' }}) }}
18 |
19 | {{ form_label(password_form.newPassword.second) }}
20 | {{ form_widget(password_form.newPassword.second, {'attr': {'class': 'form-control' }}) }}
21 |
22 |
23 |
24 | {{ form_widget(password_form.submit, {'attr': {'class': 'btn btn-primary' }}) }}
25 |
26 | {{ form_end(password_form) }}
27 |
28 |
29 |
{{ "registration.information"|trans }}
30 |
{{ "registration.information-password-policy"|trans|raw }}
31 |
32 |
--------------------------------------------------------------------------------
/templates/Start/delete_account.html.twig:
--------------------------------------------------------------------------------
1 | {{ "index.delete-headline"|trans }}
2 | {{ "index.delete-description"|trans({'%project_name%': project_name})|raw }}
3 | {{ "index.delete-button"|trans }}
4 |
--------------------------------------------------------------------------------
/templates/Start/index_spam.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block subtitle %}{{ "spam.title"|trans({'%domain%': domain}) }}{% endblock %}
4 |
5 | {% block content %}
6 |
7 |
8 |
9 |
{{ "spam.title"|trans({'%domain%': domain}) }}
10 |
11 |
{{ "spam.text"|trans }}
12 |
13 |
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/Start/recovery_token.html.twig:
--------------------------------------------------------------------------------
1 | {{ "recovery-token.headline"|trans }}
2 | {{ "recovery-token.lead"|trans }}
3 | {% if not recovery_secret_set %}
4 | {{ "recovery-token.unset"|trans }}
5 | {% endif %}
6 | {{ "index.recovery-token-button"|trans }}
7 |
--------------------------------------------------------------------------------
/templates/Start/twofactor.html.twig:
--------------------------------------------------------------------------------
1 | {{ "account.twofactor.headline"|trans }}
2 | {{ "account.twofactor.lead"|trans }}
3 | {% if not twofactor_enabled %}
4 |
5 |
6 | {{ "account.twofactor.unset"|trans }}
7 |
8 | {% endif %}
9 | {{ "account.twofactor.button"|trans }}
10 |
--------------------------------------------------------------------------------
/templates/Twofactor/twofactor_notes.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | - {{ "account.twofactor.desc-login"|trans({'%app_url%': app_url}) }}
7 | - {{ "account.twofactor.desc-lost"|trans({'%app_url%': app_url})|raw }}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/templates/User/twofactor_backup_ack.html.twig:
--------------------------------------------------------------------------------
1 | {% form_theme backupAckForm 'Form/fields.html.twig' %}
2 |
3 |
4 | {{ "account.twofactor.backup-ack-lead"|trans }}
5 |
6 |
7 |
8 | {% for key, value in twofactor_backup_codes %}
9 | {{ value }}
10 | {% endfor %}
11 |
12 |
13 | {{ form_start(backupAckForm) }}
14 | {{ form_errors(backupAckForm) }}
15 |
16 | {{ form_errors(backupAckForm.ack) }}
17 | {{ form_widget(backupAckForm.ack) }}
18 | {{ form_label(backupAckForm.ack) }}
19 |
20 |
21 | {{ form_widget(backupAckForm.submit, {'attr': {'class': 'btn btn-primary' } }) }}
22 |
23 | {{ form_end(backupAckForm) }}
24 |
--------------------------------------------------------------------------------
/templates/User/twofactor_enable.html.twig:
--------------------------------------------------------------------------------
1 | {% form_theme confirmForm 'Form/fields.html.twig' %}
2 |
3 |
4 | {{ "account.twofactor.enable-lead"|trans }}
5 |
6 |
7 |
 }})
8 |
9 | {{ form_start(confirmForm) }}
10 | {{ form_errors(confirmForm) }}
11 |
12 | {{ form_label(confirmForm.totpSecret) }}
13 | {{ form_errors(confirmForm.totpSecret) }}
14 | {{ form_widget(confirmForm.totpSecret, {'attr': {
15 | 'class': 'form-control',
16 | 'autofocus': '',
17 | 'placeholder': 'form.twofactor-login-placeholder',
18 | 'autocomplete': 'off'
19 | }}) }}
20 |
21 |
22 | {{ form_widget(confirmForm.submit, {'attr': {'class': 'btn btn-primary' } }) }}
23 |
24 | {{ form_end(confirmForm) }}
25 |
--------------------------------------------------------------------------------
/templates/_locale_switcher.html.twig:
--------------------------------------------------------------------------------
1 | {% set route = app.request.attributes.get('_route') %}
2 | {% set route_params = app.request.attributes.get('_route_params') %}
3 | {% set params = route_params|merge(app.request.query.all) %}
4 |
5 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/Command/AdminPasswordCommandTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(AdminPasswordUpdater::class)
16 | ->disableOriginalConstructor()
17 | ->getMock();
18 |
19 | $command = new AdminPasswordCommand($updater);
20 | $app = new Application();
21 | $app->add($command);
22 | $commandTester = new CommandTester($command);
23 |
24 | $commandTester->execute(['password' => 'test']);
25 |
26 | $output = $commandTester->getDisplay();
27 | $this->assertEquals('', $output);
28 |
29 | $commandTester->setInputs(['password via interactive command\n']);
30 | $commandTester->execute([]);
31 |
32 | $output = $commandTester->getDisplay();
33 | $this->assertStringContainsString('Please enter new admin password', $output);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Controller/OpenPGPControllerTest.php:
--------------------------------------------------------------------------------
1 | request('GET', '/openpgp');
14 |
15 | $this->assertResponseRedirects('/login');
16 | }
17 |
18 | public function testVisitingAuthenticated(): void
19 | {
20 | $client = static::createClient();
21 | $user = $client->getContainer()->get('doctrine')->getRepository(User::class)->findOneBy(['email' => 'user@example.org']);
22 |
23 | $client->loginUser($user);
24 |
25 | $client->request('GET', '/openpgp');
26 |
27 | $this->assertResponseIsSuccessful();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Controller/RecoveryControllerTest.php:
--------------------------------------------------------------------------------
1 | request('GET', '/recovery');
13 |
14 | $this->assertResponseIsSuccessful();
15 | }
16 |
17 | public function testVisitRecoveryWitInvalidRecoveryToken()
18 | {
19 | $client = static::createClient();
20 | $crawler = $client->request('GET', '/recovery');
21 |
22 | $form = $crawler->selectButton('Recover')->form();
23 | $form['recovery_process[email]'] = 'user@example.com';
24 | $form['recovery_process[recoveryToken]'] = 'invalid-token';
25 |
26 | $client->submit($form);
27 |
28 | $this->assertResponseIsSuccessful();
29 | $this->assertSelectorTextContains('div.alert-danger', "This token has an invalid format.");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Controller/RegistrationControllerTest.php:
--------------------------------------------------------------------------------
1 | request('GET', '/register');
13 |
14 | $voucher = $crawler->filter('input#registration_voucher')->first();
15 | $this->assertNotNull($voucher);
16 | $this->assertNull($voucher->attr('readonly'));
17 | }
18 |
19 | public function testRegisterWithVoucher(): void
20 | {
21 | $client = static::createClient();
22 | $crawler = $client->request('GET', '/register/161161');
23 |
24 | $voucher = $crawler->filter('input#registration_voucher')->first();
25 | $this->assertNotNull($voucher);
26 | $this->assertNotNull($voucher->attr('readonly'));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Controller/RoundcubeControllerTest.php:
--------------------------------------------------------------------------------
1 | request('GET', '/api/roundcube');
14 |
15 | self::assertResponseStatusCodeSame(401);
16 | }
17 |
18 | public function testGetUserAliases(): void
19 | {
20 | $client = static::createClient();
21 | $userRepository = static::getContainer()->get('doctrine')->getRepository(User::class);
22 | $client->loginUser($userRepository->findOneByEmail('user2@example.org'));
23 | $client->request('GET', '/api/roundcube');
24 |
25 | self::assertResponseIsSuccessful();
26 |
27 | $expected = [
28 | 'alias@example.org',
29 | 'alias2@example.org',
30 | ];
31 | $data = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR);
32 | self::assertEquals($expected, $data);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/EventListener/UserCreatedListenerTest.php:
--------------------------------------------------------------------------------
1 | createMock(WebhookHandler::class);
20 | $webhookHandler->expects($this->once())->method('send')->with($user, 'user.created');
21 |
22 | $listener = new UserCreatedListener($webhookHandler);
23 | $event = $this->createMock(UserCreatedEvent::class);
24 | $event->method('getUser')->willReturn($user);
25 | $listener->onUserCreated($event);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/EventListener/UserDeletedListenerTest.php:
--------------------------------------------------------------------------------
1 | createMock(WebhookHandler::class);
18 | $webhookHandler->expects($this->once())->method('send')->with($user, 'user.deleted');
19 |
20 | $listener = new UserDeletedListener($webhookHandler);
21 | $event = $this->createMock(UserDeletedEvent::class);
22 | $event->method('getUser')->willReturn($user);
23 | $listener->onUserDeleted($event);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Factory/DomainFactoryTest.php:
--------------------------------------------------------------------------------
1 | assertSame($name, $domain->getName());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Factory/VoucherFactoryTest.php:
--------------------------------------------------------------------------------
1 | getCreationTime());
18 | self::assertNotNull($voucher->getUser());
19 | self::assertNotNull($voucher->getCode());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Form/AliasDeleteTypeTest.php:
--------------------------------------------------------------------------------
1 | $password];
15 |
16 | $form = $this->factory->create(AliasDeleteType::class);
17 |
18 | $object = new Delete();
19 | $object->password = $password;
20 |
21 | // submit the data to the form directly
22 | $form->submit($formData);
23 |
24 | $this->assertTrue($form->isSynchronized());
25 | $this->assertEquals($object, $form->getData());
26 |
27 | $view = $form->createView();
28 | $children = $view->children;
29 |
30 | foreach (array_keys($formData) as $key) {
31 | $this->assertArrayHasKey($key, $children);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Form/OpenPgpKeyDeleteTypeTest.php:
--------------------------------------------------------------------------------
1 | $password];
15 |
16 | $form = $this->factory->create(OpenPgpDeleteType::class);
17 |
18 | $object = new Delete();
19 | $object->password = $password;
20 |
21 | // submit the data to the form directly
22 | $form->submit($formData);
23 |
24 | self::assertTrue($form->isSynchronized());
25 | self::assertEquals($object, $form->getData());
26 |
27 | $view = $form->createView();
28 | $children = $view->children;
29 |
30 | foreach (array_keys($formData) as $key) {
31 | self::assertArrayHasKey($key, $children);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Form/PasswordChangeTypeTest.php:
--------------------------------------------------------------------------------
1 | $password,
19 | 'newPassword' => [
20 | 'first' => $newPassword,
21 | 'second' => $newPassword,
22 | ],
23 | ];
24 |
25 | $model = new PasswordChange();
26 | $form = $this->factory->create(PasswordChangeType::class, $model);
27 |
28 | $expected = new PasswordChange();
29 | $expected->setPassword($password);
30 | $expected->setNewPassword($newPassword);
31 |
32 | $form->submit($formData);
33 |
34 | $this->assertTrue($form->isSynchronized());
35 |
36 | $this->assertEquals($expected, $model);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Form/RecoveryTokenAckTypeTest.php:
--------------------------------------------------------------------------------
1 | true,
17 | 'recoveryToken' => $uuid,
18 | ];
19 |
20 | $form = $this->factory->create(RecoveryTokenAckType::class);
21 |
22 | $object = new RecoveryTokenAck();
23 | $object->ack = true;
24 | $object->setRecoveryToken($uuid);
25 |
26 | // submit the data to the form directly
27 | $form->submit($formData);
28 |
29 | $this->assertTrue($form->isSynchronized());
30 | $this->assertEquals($object, $form->getData());
31 |
32 | $view = $form->createView();
33 | $children = $view->children;
34 |
35 | foreach (array_keys($formData) as $key) {
36 | $this->assertArrayHasKey($key, $children);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Form/RecoveryTokenTypeTest.php:
--------------------------------------------------------------------------------
1 | $password];
15 |
16 | $form = $this->factory->create(RecoveryTokenType::class);
17 |
18 | $object = new RecoveryToken();
19 | $object->password = $password;
20 |
21 | // submit the data to the form directly
22 | $form->submit($formData);
23 |
24 | $this->assertTrue($form->isSynchronized());
25 | $this->assertEquals($object, $form->getData());
26 |
27 | $view = $form->createView();
28 | $children = $view->children;
29 |
30 | foreach (array_keys($formData) as $key) {
31 | $this->assertArrayHasKey($key, $children);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Form/TwofactorConfirmTypeTest.php:
--------------------------------------------------------------------------------
1 | $totpSecret];
15 |
16 | $form = $this->factory->create(TwofactorConfirmType::class);
17 |
18 | $object = new TwofactorConfirm();
19 | $object->totpSecret = $totpSecret;
20 |
21 | // submit the data to the form directly
22 | $form->submit($formData);
23 |
24 | $this->assertTrue($form->isSynchronized());
25 | $this->assertEquals($object, $form->getData());
26 |
27 | $view = $form->createView();
28 | $children = $view->children;
29 |
30 | foreach (array_keys($formData) as $key) {
31 | $this->assertArrayHasKey($key, $children);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Form/UserDeleteTypeTest.php:
--------------------------------------------------------------------------------
1 | $password];
15 |
16 | $form = $this->factory->create(UserDeleteType::class);
17 |
18 | $object = new Delete();
19 | $object->password = $password;
20 |
21 | // submit the data to the form directly
22 | $form->submit($formData);
23 |
24 | $this->assertTrue($form->isSynchronized());
25 | $this->assertEquals($object, $form->getData());
26 |
27 | $view = $form->createView();
28 | $children = $view->children;
29 |
30 | foreach (array_keys($formData) as $key) {
31 | $this->assertArrayHasKey($key, $children);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Handler/CryptoSecretHandlerTest.php:
--------------------------------------------------------------------------------
1 | expectException(Exception::class);
22 | $this->expectExceptionMessage('salt should not be null');
23 | $secret = CryptoSecretHandler::create('message', 'password');
24 |
25 | $secret->setSalt(null);
26 | CryptoSecretHandler::decrypt($secret, 'password');
27 | }
28 |
29 | public function testDecrypt(): void
30 | {
31 | $secret = CryptoSecretHandler::create('message', 'password');
32 |
33 | self::assertNull(CryptoSecretHandler::decrypt($secret, 'wrong_password'));
34 | self::assertEquals('message', CryptoSecretHandler::decrypt($secret, 'password'));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Handler/PasswordStrengthHandlerTest.php:
--------------------------------------------------------------------------------
1 | validate($input);
20 |
21 | self::assertEquals($expected, $actual);
22 | }
23 |
24 | public function dataProvider(): array
25 | {
26 | return [
27 | ['password', ['form.weak_password']],
28 | ['Password', ['form.weak_password']],
29 | ['pässword', ['form.forbidden_char', 'form.weak_password']],
30 | ['PässwordSecure1', ['form.forbidden_char']],
31 | ['PasswördSecure1', ['form.forbidden_char']],
32 | ['PasswordSecüre1', ['form.forbidden_char']],
33 | ['PasswordSecure1\'', ['form.forbidden_char']],
34 | ['passwordpasswordpassword', []],
35 | ['PasswordSecure1', []],
36 | ['PasswordSecure$', []],
37 | ['PasswordSecure!', []],
38 | ['PasswordSecure_', []],
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Helper/PasswordGeneratorTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(PasswordHasherFactoryInterface::class)
17 | ->getMock();
18 | $passwordHasherFactory->method('getPasswordHasher')->willReturn($hasher);
19 | $updater = new PasswordUpdater($passwordHasherFactory);
20 |
21 | $user = new User();
22 | $updater->updatePassword($user, 'password');
23 |
24 | $password = $user->getPassword();
25 | self::assertNotNull($password);
26 |
27 | $updater->updatePassword($user, 'new password');
28 |
29 | self::assertNotEquals($password, $user->getPassword());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Helper/RandomStringGeneratorTest.php:
--------------------------------------------------------------------------------
1 | expectException(Exception::class);
14 | $this->expectExceptionMessage('Base64 decoding of encrypted message failed');
15 | $secret = new CryptoSecret('', '', '');
16 | $secret::decode('brokenbase64%%%');
17 | }
18 |
19 | public function testDecodeExceptionTruncated(): void
20 | {
21 | $this->expectException(Exception::class);
22 | $this->expectExceptionMessage('The encrypted message was truncated');
23 | $secret = new CryptoSecret('', '', '');
24 | $secret::decode('shortcipher');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Model/MailCryptKeyPairTest.php:
--------------------------------------------------------------------------------
1 | getPrivateKey());
15 | self::assertEquals('public', $keyPair->getPublicKey());
16 |
17 | $keyPair->erase();
18 |
19 | self::assertNull($keyPair->getPrivateKey());
20 | self::assertNull($keyPair->getPublicKey());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Security/UserCheckerTest.php:
--------------------------------------------------------------------------------
1 | userChecker = new UserChecker();
15 | }
16 |
17 | public function testPreAuth(): void
18 | {
19 | $user = new User();
20 |
21 | $this->userChecker->checkPreAuth($user);
22 |
23 | $deletedUser = new User();
24 | $deletedUser->setDeleted(true);
25 |
26 | $this->expectException('Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException');
27 | $this->userChecker->checkPreAuth($deletedUser);
28 | }
29 |
30 | public function testPostAuth(): void
31 | {
32 | $user = new User();
33 |
34 | $this->userChecker->checkPostAuth($user);
35 |
36 | $this->expectNotToPerformAssertions();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/autoload.php:
--------------------------------------------------------------------------------
1 | load(__DIR__.'/../.env.test');
15 | }
16 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | bootEnv(dirname(__DIR__).'/.env');
9 | }
10 |
11 | if ($_SERVER['APP_DEBUG']) {
12 | umask(0000);
13 | }
14 |
--------------------------------------------------------------------------------
/tests/test_checkpassword_login.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | openssl s_client -connect 192.168.60.99:995 -quiet <