Skip to content

Commit

Permalink
Merge pull request #639 from systemli/feat/user_aliases_api
Browse files Browse the repository at this point in the history
feat: Add user API endpoint to get list of aliases
  • Loading branch information
doobry-systemli authored Sep 27, 2024
2 parents b3437c4 + 29ee905 commit de9526f
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 26 deletions.
6 changes: 6 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,9 @@ KEYCLOAK_API_ENABLED=false
KEYCLOAK_API_IP_ALLOWLIST="127.0.0.1, ::1"
# Warning: set a secure access token
KEYCLOAK_API_ACCESS_TOKEN="insecure"

### Enable roundcube API ###
# Set to `true` to enable roundcube API
ROUNDCUBE_API_ENABLED=false
# Access is restricted to these IPs (supports subnets like `10.0.0.1/24`)
ROUNDCUBE_API_IP_ALLOWLIST="127.0.0.1, ::1"
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ WKD_FORMAT="advanced"
KEYCLOAK_API_ENABLED=true
KEYCLOAK_API_IP_ALLOWLIST="127.0.0.1, ::1"
KEYCLOAK_API_ACCESS_TOKEN="insecure"
ROUNDCUBE_API_ENABLED=true
ROUNDCUBE_API_IP_ALLOWLIST="127.0.0.1, ::1"
10 changes: 5 additions & 5 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ jobs:
bin/console cache:clear --env=test
bin/behat --format progress
- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# - name: SonarCloud Scan
# uses: sonarsource/sonarcloud-github-action@master
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

test-mariadb:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/psalm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v4

- name: Psalm
uses: docker://vimeo/psalm-github-actions
uses: docker://ghcr.io/psalm/psalm-github-actions
with:
security_analysis: true
report_file: results.sarif
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 3.9.0 (2024.09.27)

* Add roundcube API endpoint to get list of aliases
* Fix MailCryptKeyHandler create/update (#629)
* Update dependencies

# 3.8.0 (2024.05.30)

* Add Support for TOTP in KeyCloak API
Expand Down
12 changes: 12 additions & 0 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ security:
provider: keycloak
access_token:
token_handler: App\Security\KeycloakAccessTokenHandler
roundcube:
pattern: ^/api/roundcube
stateless: true
provider: user
http_basic:
realm: Roundcube API
main:
pattern: ^/
provider: user
Expand Down Expand Up @@ -163,3 +169,9 @@ security:
allow_if: "'%env(KEYCLOAK_API_ENABLED)%' == 'true' and is_granted('ROLE_KEYCLOAK')",
}
- { path: "^/api/keycloak", roles: ROLE_NO_ACCESS }
- {
path: "^/api/roundcube",
ips: "%env(ROUNDCUBE_API_IP_ALLOWLIST)%",
allow_if: "'%env(ROUNDCUBE_API_ENABLED)%' == 'true' and is_granted('ROLE_USER')",
}
- { path: "^/api/roundcube", roles: ROLE_NO_ACCESS }
27 changes: 27 additions & 0 deletions src/Controller/RoundcubeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Controller;

use App\Entity\Alias;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class RoundcubeController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $manager,
) {
}

#[Route(path: '/api/roundcube', name: 'api_roundcube', methods: ['GET'], stateless: true)]
public function getUserAliases(): Response
{
$user = $this->getUser();

$aliases = $this->manager->getRepository(Alias::class)->findByUser($user);
$aliasSources = array_map(static function ($alias) { return $alias->getSource(); }, $aliases);
return $this->json($aliasSources);
}
}
25 changes: 6 additions & 19 deletions src/DataFixtures/LoadAliasData.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,20 @@ class LoadAliasData extends Fixture implements FixtureGroupInterface, DependentF
*/
public function load(ObjectManager $manager): void
{
$user = $manager->getRepository(User::class)->findByEmail('admin@example.org');
$user = $manager->getRepository(User::class)->findByEmail('user2@example.org');

for ($i = 1; $i < 5; ++$i) {
$alias = AliasFactory::create($user, null);

$manager->persist($alias);
}

$users = $manager->getRepository(User::class)->findAll();

for ($i = 1; $i < 500; ++$i) {
$alias = AliasFactory::create($users[random_int(0, count($users) - 1)], 'alias' . $i);

$manager->persist($alias);

if (($i % 100) === 0) {
$manager->flush();
}
}
$alias = AliasFactory::create($user, 'alias');
$manager->persist($alias);
$alias2 = AliasFactory::create($user, 'alias2');
$manager->persist($alias2);

$manager->flush();
$manager->clear();
}

public static function getGroups(): array
{
return ['advanced'];
return ['basic'];
}

public function getDependencies(): array
Expand Down
54 changes: 54 additions & 0 deletions src/DataFixtures/LoadRandomAliasData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\DataFixtures;

use App\Entity\User;
use App\Factory\AliasFactory;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;

class LoadRandomAliasData extends Fixture implements FixtureGroupInterface, DependentFixtureInterface
{
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager): void
{
$user = $manager->getRepository(User::class)->findByEmail('admin@example.org');

for ($i = 1; $i < 5; ++$i) {
$alias = AliasFactory::create($user, null);

$manager->persist($alias);
}

$users = $manager->getRepository(User::class)->findAll();

for ($i = 1; $i < 500; ++$i) {
$alias = AliasFactory::create($users[random_int(0, count($users) - 1)], 'alias' . $i);

$manager->persist($alias);

if (($i % 100) === 0) {
$manager->flush();
}
}

$manager->flush();
$manager->clear();
}

public static function getGroups(): array
{
return ['advanced'];
}

public function getDependencies(): array
{
return [
LoadUserData::class,
];
}
}
1 change: 1 addition & 0 deletions src/DataFixtures/LoadUserData.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class LoadUserData extends AbstractUserData implements DependentFixtureInterface
private array $users = [
['email' => 'admin@example.org', 'roles' => [Roles::ADMIN], 'totp' => false],
['email' => 'user@example.org', 'roles' => [Roles::USER], 'totp' => false],
['email' => 'user2@example.org', 'roles' => [Roles::USER], 'totp' => false],
['email' => 'totp@example.org', 'roles' => [Roles::USER], 'totp' => true],
['email' => 'spam@example.org', 'roles' => [Roles::SPAM], 'totp' => false],
['email' => 'support@example.org', 'roles' => [Roles::MULTIPLIER], 'totp' => false],
Expand Down
2 changes: 1 addition & 1 deletion tests/Controller/KeycloakControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function testGetUsersCount(): void
self::assertResponseIsSuccessful();

$data = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR);
self::assertEquals(6, $data);
self::assertEquals(7, $data);
}

public function testGetOneUser(): void
Expand Down
34 changes: 34 additions & 0 deletions tests/Controller/RoundcubeControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Tests\Controller;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class RoundcubeControllerTest extends WebTestCase
{
public function testGetUserAliasesWrongCredentials(): void
{
$client = static::createClient();
$client->request('GET', '/api/roundcube');

self::assertResponseStatusCodeSame(401);
}

public function testGetUserAliases(): void
{
$client = static::createClient();
$userRepository = static::getContainer()->get('doctrine')->getRepository(User::class);
$client->loginUser($userRepository->findOneByEmail('user2@example.org'));
$client->request('GET', '/api/roundcube');

self::assertResponseIsSuccessful();

$expected = [
'alias@example.org',
'alias2@example.org',
];
$data = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR);
self::assertEquals($expected, $data);
}
}

0 comments on commit de9526f

Please sign in to comment.