feat: integrated OAuth2 server
This commit is contained in:
@@ -56,3 +56,9 @@ SFTP_XML_EXPORT_HOST=
|
|||||||
SFTP_XML_EXPORT_PORT=
|
SFTP_XML_EXPORT_PORT=
|
||||||
SFTP_XML_EXPORT_USER=
|
SFTP_XML_EXPORT_USER=
|
||||||
SFTP_XML_EXPORT_PASSWORD=
|
SFTP_XML_EXPORT_PASSWORD=
|
||||||
|
|
||||||
|
###> league/oauth2-server-bundle ###
|
||||||
|
OAUTH_PRIVATE_KEY=%kernel.project_dir%/config/secret/private.key
|
||||||
|
OAUTH_PUBLIC_KEY=%kernel.project_dir%/config/secret/public.key
|
||||||
|
OAUTH_ENCRYPTION_KEY=580084fd179e67399467f59ee96658ac
|
||||||
|
###< league/oauth2-server-bundle ###
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ yarn-error.log
|
|||||||
###< symfony/webpack-encore-bundle ###
|
###< symfony/webpack-encore-bundle ###
|
||||||
|
|
||||||
/http-client.private.env.json
|
/http-client.private.env.json
|
||||||
|
|
||||||
|
###> friendsofphp/php-cs-fixer ###
|
||||||
|
/.php-cs-fixer.php
|
||||||
|
/.php-cs-fixer.cache
|
||||||
|
###< friendsofphp/php-cs-fixer ###
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$finder = (new PhpCsFixer\Finder())
|
||||||
|
->in(__DIR__)
|
||||||
|
->exclude('var')
|
||||||
|
;
|
||||||
|
|
||||||
|
return (new PhpCsFixer\Config())
|
||||||
|
->setRules([
|
||||||
|
'@Symfony' => true,
|
||||||
|
])
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
### user profile minimum
|
||||||
|
GET https://myep-next.ddev.site/api/userinfo
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_email")}}
|
||||||
|
|
||||||
|
### user profile full
|
||||||
|
GET https://myep-next.ddev.site/api/userinfo
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_profile")}}
|
||||||
|
|
||||||
|
### user crm attributes
|
||||||
|
GET https://myep-next.ddev.site/api/crm-attributes
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_profile")}}
|
||||||
|
|
||||||
|
### API countries
|
||||||
|
GET https://myep-next.ddev.site/api/countries
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_api")}}
|
||||||
|
|
||||||
|
### API hotels
|
||||||
|
GET https://myep-next.ddev.site/api/hotels
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_api")}}
|
||||||
|
|
||||||
|
### API hotel
|
||||||
|
GET https://myep-next.ddev.site/api/hotels/198924
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_api")}}
|
||||||
|
|
||||||
|
### API pickups
|
||||||
|
GET https://myep-next.ddev.site/api/pickups
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_api")}}
|
||||||
|
|
||||||
|
### API pickup
|
||||||
|
GET https://myep-next.ddev.site/api/pickups/1
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{$auth.token("oauth2_api")}}
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"knplabs/knp-menu-bundle": "^3.4",
|
"knplabs/knp-menu-bundle": "^3.4",
|
||||||
"league/flysystem-bundle": "^3.4",
|
"league/flysystem-bundle": "^3.4",
|
||||||
"league/flysystem-sftp-v3": "^3.29",
|
"league/flysystem-sftp-v3": "^3.29",
|
||||||
|
"league/oauth2-server-bundle": "^0.11.0",
|
||||||
"nelexa/zip": "^4.0",
|
"nelexa/zip": "^4.0",
|
||||||
"nesbot/carbon": "^3.8",
|
"nesbot/carbon": "^3.8",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6",
|
"phpdocumentor/reflection-docblock": "^5.6",
|
||||||
@@ -109,6 +110,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"deployer/deployer": "^7.5",
|
"deployer/deployer": "^7.5",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.75",
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^9.5",
|
||||||
"symfony/browser-kit": "6.4.*",
|
"symfony/browser-kit": "6.4.*",
|
||||||
"symfony/css-selector": "6.4.*",
|
"symfony/css-selector": "6.4.*",
|
||||||
|
|||||||
Generated
+1967
-1
File diff suppressed because it is too large
Load Diff
@@ -15,4 +15,5 @@ return [
|
|||||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||||
Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true],
|
Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true],
|
||||||
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
|
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
|
||||||
|
League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
league_oauth2_server:
|
||||||
|
authorization_server:
|
||||||
|
private_key: '%env(resolve:OAUTH_PRIVATE_KEY)%'
|
||||||
|
private_key_passphrase: null
|
||||||
|
encryption_key: '%env(resolve:OAUTH_ENCRYPTION_KEY)%'
|
||||||
|
enable_client_credentials_grant: true
|
||||||
|
enable_auth_code_grant: true
|
||||||
|
enable_refresh_token_grant: false
|
||||||
|
enable_password_grant: false
|
||||||
|
access_token_ttl: PT10M
|
||||||
|
resource_server:
|
||||||
|
public_key: '%env(resolve:OAUTH_PUBLIC_KEY)%'
|
||||||
|
scopes:
|
||||||
|
available: ['email','profile','api']
|
||||||
|
default: ['email']
|
||||||
|
persistence:
|
||||||
|
doctrine: null
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
league_oauth2_server:
|
||||||
|
persistence:
|
||||||
|
in_memory: null
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
services:
|
||||||
|
# Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories)
|
||||||
|
Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||||
|
Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||||
|
Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||||
|
Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||||
|
Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||||
|
Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory'
|
||||||
|
|
||||||
|
nyholm.psr7.psr17_factory:
|
||||||
|
class: Nyholm\Psr7\Factory\Psr17Factory
|
||||||
@@ -12,12 +12,14 @@ security:
|
|||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
security: false
|
security: false
|
||||||
|
api_token:
|
||||||
|
pattern: ^/token$
|
||||||
|
security: false
|
||||||
api:
|
api:
|
||||||
lazy: true
|
|
||||||
stateless: true
|
|
||||||
pattern: ^/api
|
pattern: ^/api
|
||||||
custom_authenticator:
|
security: true
|
||||||
- App\Security\ApiKeyAuthenticator
|
stateless: true
|
||||||
|
oauth2: true
|
||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
@@ -30,7 +32,7 @@ security:
|
|||||||
# Easy way to control access for large sections of your site
|
# Easy way to control access for large sections of your site
|
||||||
# Note: Only the *first* access control that matches will be used
|
# Note: Only the *first* access control that matches will be used
|
||||||
access_control:
|
access_control:
|
||||||
- { path: ^/api, roles: ROLE_API }
|
- { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED }
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
security:
|
security:
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
league_oauth2_server:
|
||||||
|
resource: '@LeagueOAuth2ServerBundle/config/routes.php'
|
||||||
|
type: php
|
||||||
@@ -27,10 +27,6 @@ services:
|
|||||||
- '../src/Entity/'
|
- '../src/Entity/'
|
||||||
- '../src/Kernel.php'
|
- '../src/Kernel.php'
|
||||||
|
|
||||||
App\Security\ApiKeyAuthenticator:
|
|
||||||
arguments:
|
|
||||||
$apiKeys: '%env(csv:API_KEYS)%'
|
|
||||||
|
|
||||||
App\BusProNet\ApiClient:
|
App\BusProNet\ApiClient:
|
||||||
arguments:
|
arguments:
|
||||||
$logger: '@monolog.logger.bpn'
|
$logger: '@monolog.logger.bpn'
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"dev": {
|
||||||
|
"Security": {
|
||||||
|
"Auth": {
|
||||||
|
"oauth2_email": {
|
||||||
|
"Type": "OAuth2",
|
||||||
|
"Grant Type": "Authorization Code",
|
||||||
|
"Client ID": "{{oauth2_client_id}}",
|
||||||
|
"Client Secret": "{{oauth2_client_secret}}",
|
||||||
|
"Auth URL": "https://myep-next.ddev.site/authorize",
|
||||||
|
"Token URL": "https://myep-next.ddev.site/token",
|
||||||
|
"Redirect URL": "https://myep-team.ddev.site/auth/check",
|
||||||
|
"Scope": "email"
|
||||||
|
},
|
||||||
|
"oauth2_profile": {
|
||||||
|
"Type": "OAuth2",
|
||||||
|
"Grant Type": "Authorization Code",
|
||||||
|
"Client ID": "{{oauth2_client_id}}",
|
||||||
|
"Client Secret": "{{oauth2_client_secret}}",
|
||||||
|
"Auth URL": "https://myep-next.ddev.site/authorize",
|
||||||
|
"Token URL": "https://myep-next.ddev.site/token",
|
||||||
|
"Redirect URL": "https://myep-team.ddev.site/auth/check",
|
||||||
|
"Scope": "email profile"
|
||||||
|
},
|
||||||
|
"oauth2_api": {
|
||||||
|
"Type": "OAuth2",
|
||||||
|
"Grant Type": "Client Credentials",
|
||||||
|
"Client ID": "{{oauth2_api_client_id}}",
|
||||||
|
"Client Secret": "{{oauth2_api_client_secret}}",
|
||||||
|
"Auth URL": "https://myep-next.ddev.site/authorize",
|
||||||
|
"Token URL": "https://myep-next.ddev.site/token",
|
||||||
|
"Scope": "api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250424152547 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE oauth2_access_token (identifier CHAR(80) NOT NULL, client VARCHAR(32) NOT NULL, expiry DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', user_identifier VARCHAR(128) DEFAULT NULL, scopes TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_scope)', revoked TINYINT(1) NOT NULL, INDEX IDX_454D9673C7440455 (client), PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE oauth2_authorization_code (identifier CHAR(80) NOT NULL, client VARCHAR(32) NOT NULL, expiry DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', user_identifier VARCHAR(128) DEFAULT NULL, scopes TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_scope)', revoked TINYINT(1) NOT NULL, INDEX IDX_509FEF5FC7440455 (client), PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE oauth2_client (identifier VARCHAR(32) NOT NULL, name VARCHAR(128) NOT NULL, secret VARCHAR(128) DEFAULT NULL, redirect_uris TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_redirect_uri)', grants TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_grant)', scopes TEXT DEFAULT NULL COMMENT '(DC2Type:oauth2_scope)', active TINYINT(1) NOT NULL, allow_plain_text_pkce TINYINT(1) DEFAULT 0 NOT NULL, PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE oauth2_refresh_token (identifier CHAR(80) NOT NULL, access_token CHAR(80) DEFAULT NULL, expiry DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', revoked TINYINT(1) NOT NULL, INDEX IDX_4DD90732B6A2DD68 (access_token), PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE oauth2_access_token ADD CONSTRAINT FK_454D9673C7440455 FOREIGN KEY (client) REFERENCES oauth2_client (identifier) ON DELETE CASCADE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE oauth2_authorization_code ADD CONSTRAINT FK_509FEF5FC7440455 FOREIGN KEY (client) REFERENCES oauth2_client (identifier) ON DELETE CASCADE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE oauth2_refresh_token ADD CONSTRAINT FK_4DD90732B6A2DD68 FOREIGN KEY (access_token) REFERENCES oauth2_access_token (identifier) ON DELETE SET NULL
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE oauth2_access_token DROP FOREIGN KEY FK_454D9673C7440455
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE oauth2_authorization_code DROP FOREIGN KEY FK_509FEF5FC7440455
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE oauth2_refresh_token DROP FOREIGN KEY FK_4DD90732B6A2DD68
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE oauth2_access_token
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE oauth2_authorization_code
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE oauth2_client
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE oauth2_refresh_token
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\BusProNet;
|
namespace App\BusProNet;
|
||||||
|
|
||||||
use App\BusProNet\XmlParser\ApiResponseParser;
|
|
||||||
use App\BusProNet\DataProcessor\BookingDataProcessor;
|
use App\BusProNet\DataProcessor\BookingDataProcessor;
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
use App\BusProNet\Exception\ApiClientException;
|
||||||
use App\BusProNet\Exception\ResponseParserException;
|
use App\BusProNet\Exception\ResponseParserException;
|
||||||
@@ -12,6 +11,8 @@ use App\BusProNet\Model\BookingUpdate;
|
|||||||
use App\BusProNet\Model\CrmAttributes;
|
use App\BusProNet\Model\CrmAttributes;
|
||||||
use App\BusProNet\Model\Notification;
|
use App\BusProNet\Model\Notification;
|
||||||
use App\BusProNet\Model\PersonalData;
|
use App\BusProNet\Model\PersonalData;
|
||||||
|
use App\BusProNet\Traits\ApiClientTrait;
|
||||||
|
use App\BusProNet\XmlParser\ApiResponseParser;
|
||||||
use App\Form\Model\BookingData;
|
use App\Form\Model\BookingData;
|
||||||
use App\Form\Model\RegistrationData;
|
use App\Form\Model\RegistrationData;
|
||||||
use League\Flysystem\FilesystemException;
|
use League\Flysystem\FilesystemException;
|
||||||
@@ -39,7 +40,7 @@ class ApiClient
|
|||||||
private readonly ApiResponseParser $responseParser,
|
private readonly ApiResponseParser $responseParser,
|
||||||
private readonly FilesystemOperator $xmlDump,
|
private readonly FilesystemOperator $xmlDump,
|
||||||
private readonly LoggerInterface $logger,
|
private readonly LoggerInterface $logger,
|
||||||
array $options
|
array $options,
|
||||||
) {
|
) {
|
||||||
$this->config = $this->resolveOptions($options);
|
$this->config = $this->resolveOptions($options);
|
||||||
}
|
}
|
||||||
@@ -107,7 +108,7 @@ class ApiClient
|
|||||||
string $email,
|
string $email,
|
||||||
string $password,
|
string $password,
|
||||||
PersonalData $personalData,
|
PersonalData $personalData,
|
||||||
bool $debug = false
|
bool $debug = false,
|
||||||
): Notification|PersonalData {
|
): Notification|PersonalData {
|
||||||
$data = [
|
$data = [
|
||||||
'user' => $this->config['bpn_username'],
|
'user' => $this->config['bpn_username'],
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\BusProNet\DataProvider;
|
namespace App\BusProNet\DataProvider;
|
||||||
|
|
||||||
use App\BusProNet\ApiClient;
|
use App\BusProNet\ApiClient;
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
|
||||||
use App\BusProNet\Model\Country;
|
use App\BusProNet\Model\Country;
|
||||||
use Psr\Cache\InvalidArgumentException;
|
use Psr\Cache\InvalidArgumentException;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
@@ -12,7 +11,8 @@ use Symfony\Contracts\Cache\ItemInterface;
|
|||||||
class CountryDataProvider
|
class CountryDataProvider
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache)
|
public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function getAll(): array
|
public function getAll(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
|||||||
class CountryChoiceLoader implements ChoiceLoaderInterface
|
class CountryChoiceLoader implements ChoiceLoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CountryDataProvider $countries, private readonly string $property)
|
public function __construct(private readonly CountryDataProvider $countries, private readonly string $property)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function loadChoiceList(callable $value = null): ChoiceListInterface
|
public function loadChoiceList(?callable $value = null): ChoiceListInterface
|
||||||
{
|
{
|
||||||
$choices = [];
|
$choices = [];
|
||||||
/** @var Country[] $countries */
|
/** @var Country[] $countries */
|
||||||
@@ -25,15 +26,14 @@ class CountryChoiceLoader implements ChoiceLoaderInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new ArrayChoiceList($choices);
|
return new ArrayChoiceList($choices);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadChoicesForValues(array $values, callable $value = null): array
|
public function loadChoicesForValues(array $values, ?callable $value = null): array
|
||||||
{
|
{
|
||||||
return $values;
|
return $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadValuesForChoices(array $choices, callable $value = null): array
|
public function loadValuesForChoices(array $choices, ?callable $value = null): array
|
||||||
{
|
{
|
||||||
return $choices;
|
return $choices;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
class CountryType extends AbstractType
|
class CountryType extends AbstractType
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CountryDataProvider $countries)
|
public function __construct(private readonly CountryDataProvider $countries)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function getParent(): string
|
public function getParent(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ namespace App\BusProNet\Model;
|
|||||||
class BaseData
|
class BaseData
|
||||||
{
|
{
|
||||||
public function __construct(private readonly array $items)
|
public function __construct(private readonly array $items)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function getItems(): array
|
public function getItems(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class CrmSelectionGroup
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Maps BusPro IDs of CRM selection groups representing
|
* Maps BusPro IDs of CRM selection groups representing
|
||||||
* included services
|
* included services.
|
||||||
|
*
|
||||||
* @var array|string[]
|
* @var array|string[]
|
||||||
*/
|
*/
|
||||||
public static array $includedServicesMapping = [
|
public static array $includedServicesMapping = [
|
||||||
|
|||||||
@@ -64,4 +64,36 @@ class PersonalData
|
|||||||
'bemerkung' => $this->remarks,
|
'bemerkung' => $this->remarks,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getClaims(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => $this->communication->email,
|
||||||
|
'profile' => [
|
||||||
|
'first_name' => $this->firstName,
|
||||||
|
'last_name' => $this->name,
|
||||||
|
'title' => $this->title,
|
||||||
|
'salutation' => $this->salutation,
|
||||||
|
'gender' => $this->gender,
|
||||||
|
'nationality' => $this->nationality,
|
||||||
|
'address' => [
|
||||||
|
'street' => $this->address->street,
|
||||||
|
'postcode' => $this->address->postCode,
|
||||||
|
'city' => $this->address->city,
|
||||||
|
'disctrict' => $this->address->district,
|
||||||
|
'country' => $this->address->country,
|
||||||
|
],
|
||||||
|
'communication' => [
|
||||||
|
'email' => $this->communication->email,
|
||||||
|
'mobile' => $this->communication->mobile,
|
||||||
|
'phone' => $this->communication->phone,
|
||||||
|
'newsletter' => $this->communication->newsletter,
|
||||||
|
],
|
||||||
|
'height' => $this->height,
|
||||||
|
'weight' => $this->weight,
|
||||||
|
'shoe_size' => $this->shoeSize,
|
||||||
|
'remarks' => $this->remarks,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\BusProNet;
|
namespace App\BusProNet\Traits;
|
||||||
|
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
use App\BusProNet\Exception\ApiClientException;
|
||||||
|
|
||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\BusProNet;
|
namespace App\BusProNet\Traits;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Carbon\Exceptions\InvalidFormatException;
|
use Carbon\Exceptions\InvalidFormatException;
|
||||||
@@ -52,6 +52,7 @@ trait TypeConversionTrait
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$date = Carbon::createFromFormat('d.m.Y', $string)->startOfDay();
|
$date = Carbon::createFromFormat('d.m.Y', $string)->startOfDay();
|
||||||
|
|
||||||
return $date->toDateTimeImmutable();
|
return $date->toDateTimeImmutable();
|
||||||
} catch (InvalidFormatException $e) {
|
} catch (InvalidFormatException $e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -66,6 +67,7 @@ trait TypeConversionTrait
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16))->startOfDay();
|
$dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16))->startOfDay();
|
||||||
|
|
||||||
return $dateTime->toDateTimeImmutable();
|
return $dateTime->toDateTimeImmutable();
|
||||||
} catch (InvalidFormatException $e) {
|
} catch (InvalidFormatException $e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\BusProNet;
|
namespace App\BusProNet\Traits;
|
||||||
|
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\BusProNet\XmlLoader;
|
namespace App\BusProNet\XmlLoader;
|
||||||
|
|
||||||
use App\BusProNet\TypeConversionTrait;
|
use App\BusProNet\Traits\TypeConversionTrait;
|
||||||
use App\BusProNet\XmlParserTrait;
|
use App\BusProNet\Traits\XmlParserTrait;
|
||||||
use League\Flysystem\FilesystemOperator;
|
use League\Flysystem\FilesystemOperator;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TravelLoader extends AbstractLoader
|
|||||||
private readonly HotelLoader $hotelDataLoader,
|
private readonly HotelLoader $hotelDataLoader,
|
||||||
CacheInterface $cache,
|
CacheInterface $cache,
|
||||||
private readonly string $travelInfoBaseUrl,
|
private readonly string $travelInfoBaseUrl,
|
||||||
FileSystemOperator $xmlExport,
|
FilesystemOperator $xmlExport,
|
||||||
) {
|
) {
|
||||||
parent::__construct($cache, $xmlExport);
|
parent::__construct($cache, $xmlExport);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\BusProNet\XmlParser;
|
namespace App\BusProNet\XmlParser;
|
||||||
|
|
||||||
use App\BusProNet\TypeConversionTrait;
|
use App\BusProNet\Traits\TypeConversionTrait;
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
abstract class AbstractParser
|
abstract class AbstractParser
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use App\BusProNet\Model\CrmAction;
|
|||||||
use App\BusProNet\Model\CrmAttributes;
|
use App\BusProNet\Model\CrmAttributes;
|
||||||
use App\BusProNet\Model\CrmSelection;
|
use App\BusProNet\Model\CrmSelection;
|
||||||
use App\BusProNet\Model\CrmSelectionGroup;
|
use App\BusProNet\Model\CrmSelectionGroup;
|
||||||
use App\BusProNet\TypeConversionTrait;
|
use App\BusProNet\Traits\TypeConversionTrait;
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
class CrmAttributesResponseParser
|
class CrmAttributesResponseParser
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ class DocumentsParser
|
|||||||
});
|
});
|
||||||
|
|
||||||
return new File('reisedokumente.zip', $zip->outputAsString(), 'application/zip');
|
return new File('reisedokumente.zip', $zip->outputAsString(), 'application/zip');
|
||||||
}
|
} catch (ZipException $e) {
|
||||||
catch (ZipException $e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class RoomsParser extends AbstractParser
|
|||||||
|
|
||||||
$result->each(function (Crawler $node) use (&$rooms) {
|
$result->each(function (Crawler $node) use (&$rooms) {
|
||||||
$room = new Room();
|
$room = new Room();
|
||||||
$room->id = (int) $node->attr('idzimmer');;
|
$room->id = (int) $node->attr('idzimmer');
|
||||||
$room->label = $node->attr('zimmer');
|
$room->label = $node->attr('zimmer');
|
||||||
$room->category = $node->attr('kategorie');
|
$room->category = $node->attr('kategorie');
|
||||||
$room->boardId = (int) $node->attr('idverpflegung');
|
$room->boardId = (int) $node->attr('idverpflegung');
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
|
|
||||||
#[AsCommand(
|
#[AsCommand(
|
||||||
name: 'app:cleanup:xml-dumps',
|
name: 'app:cleanup:xml-dumps',
|
||||||
description: 'Removes XML dumps of BPN requests/responses older than a week')
|
description: 'Removes XML dumps of BPN requests/responses older than a week'
|
||||||
]
|
)]
|
||||||
class CleanupXMLDumpsCommand extends Command
|
class CleanupXMLDumpsCommand extends Command
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ use App\BusProNet\DataProvider\CountryDataProvider;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[Route('/api')]
|
#[Route('/api')]
|
||||||
|
#[IsGranted('ROLE_OAUTH2_API')]
|
||||||
class CountryController extends AbstractController
|
class CountryController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CountryDataProvider $dataProvider)
|
public function __construct(private readonly CountryDataProvider $dataProvider)
|
||||||
|
|||||||
+11
-10
@@ -5,28 +5,29 @@ namespace App\Controller\Api;
|
|||||||
use App\BusProNet\ApiClient;
|
use App\BusProNet\ApiClient;
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
use App\BusProNet\Exception\ApiClientException;
|
||||||
use App\BusProNet\Model\Notification;
|
use App\BusProNet\Model\Notification;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Security\Crypt;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[Route('/api')]
|
#[Route('/api')]
|
||||||
class CrmAttributesController extends AbstractController
|
#[IsGranted('ROLE_OAUTH2_PROFILE')]
|
||||||
|
class CrmAttributeController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly APiClient $apiClient)
|
public function __construct(private readonly ApiClient $apiClient, private readonly Crypt $crypt)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/crm-attributes', name: 'api_crm_attributes', methods: ['GET'])]
|
#[Route('/crm-attributes', name: 'api_crm_attributes', methods: ['GET'])]
|
||||||
public function index(Request $request): JsonResponse
|
public function index(): JsonResponse
|
||||||
{
|
{
|
||||||
$email = $request->headers->get('x-bpn-email');
|
/** @var User $user */
|
||||||
$password = $request->headers->get('x-bpn-password');
|
$user = $this->getUser();
|
||||||
|
$email = $user->getEmail();
|
||||||
if (null === $email || null === $password) {
|
$password = $this->crypt->decrypt($user->getPassword());
|
||||||
return new JsonResponse(['message' => 'Invalid credentials'], Response::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$data = $this->apiClient->getCrmAttributes($email, $password);
|
$data = $this->apiClient->getCrmAttributes($email, $password);
|
||||||
@@ -7,15 +7,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[Route('/api')]
|
#[Route('/api')]
|
||||||
|
#[IsGranted('ROLE_OAUTH2_API')]
|
||||||
class HotelController extends AbstractController
|
class HotelController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly HotelLoader $xmlLoader)
|
public function __construct(private readonly HotelLoader $xmlLoader)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/hotels', name: 'api_hotel_all')]
|
#[Route('/hotels', name: 'api_hotels_all')]
|
||||||
public function index(): JsonResponse
|
public function index(): JsonResponse
|
||||||
{
|
{
|
||||||
$hotels = $this->xmlLoader->loadAll();
|
$hotels = $this->xmlLoader->loadAll();
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Api;
|
|
||||||
|
|
||||||
use App\BusProNet\ApiClient;
|
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
|
||||||
use App\BusProNet\Model\Notification;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
|
||||||
|
|
||||||
#[Route('/api')]
|
|
||||||
class PersonalDataController extends AbstractController
|
|
||||||
{
|
|
||||||
public function __construct(private readonly APiClient $apiClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/personal-data', name: 'api_personal_data', methods: ['GET'])]
|
|
||||||
public function index(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$email = $request->headers->get('x-bpn-email');
|
|
||||||
$password = $request->headers->get('x-bpn-password');
|
|
||||||
|
|
||||||
if (null === $email || null === $password) {
|
|
||||||
return new JsonResponse(['message' => 'Invalid credentials'], Response::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$data = $this->apiClient->getPersonalData($email, $password);
|
|
||||||
|
|
||||||
if ($data instanceof Notification) {
|
|
||||||
return new JsonResponse(['message' => $data->message, 'code' => $data->code], Response::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->json($data);
|
|
||||||
} catch (ApiClientException $e) {
|
|
||||||
return new JsonResponse(['message' => $e->getMessage()], Response::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,15 +7,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[Route('/api')]
|
#[Route('/api')]
|
||||||
|
#[IsGranted('ROLE_OAUTH2_API')]
|
||||||
class PickupController extends AbstractController
|
class PickupController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly PickupLoader $xmlLoader)
|
public function __construct(private readonly PickupLoader $xmlLoader)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/pickups', name: 'api_pickup_all')]
|
#[Route('/pickups', name: 'api_pickups_all')]
|
||||||
public function index(): JsonResponse
|
public function index(): JsonResponse
|
||||||
{
|
{
|
||||||
$pickups = $this->xmlLoader->loadAll();
|
$pickups = $this->xmlLoader->loadAll();
|
||||||
@@ -23,7 +25,7 @@ class PickupController extends AbstractController
|
|||||||
return $this->json($pickups, Response::HTTP_OK, [], ['groups' => 'api:list']);
|
return $this->json($pickups, Response::HTTP_OK, [], ['groups' => 'api:list']);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/pickups/{id}', name: 'api_pickup_single')]
|
#[Route('/pickups/{id}', name: 'api_pickups_single')]
|
||||||
public function single(int $id): JsonResponse
|
public function single(int $id): JsonResponse
|
||||||
{
|
{
|
||||||
$pickup = $this->xmlLoader->loadById($id);
|
$pickup = $this->xmlLoader->loadById($id);
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Api;
|
|
||||||
|
|
||||||
use App\BusProNet\ApiClient;
|
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
|
||||||
use Symfony\Component\Validator\Constraints\Email;
|
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|
||||||
|
|
||||||
#[Route('/api')]
|
|
||||||
class ResetPasswordController extends AbstractController
|
|
||||||
{
|
|
||||||
public function __construct(private readonly ApiClient $apiClient, private readonly ValidatorInterface $validator)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/reset-password', name: 'api_reset_password', methods: ['POST'])]
|
|
||||||
public function index(Request $request): Response
|
|
||||||
{
|
|
||||||
$payload = json_decode($request->getContent(), true);
|
|
||||||
$email = $payload['email'] ?? null;
|
|
||||||
|
|
||||||
if (null === $email) {
|
|
||||||
return new JsonResponse(['message' => 'Invalid email'], Response::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
$violationList = $this->validator->validate($email, [new Email([
|
|
||||||
'mode' => 'strict',
|
|
||||||
])]);
|
|
||||||
|
|
||||||
if (0 < count($violationList)) {
|
|
||||||
return new JsonResponse(['message' => 'Invalid email'], Response::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->apiClient->resetPassword($email);
|
|
||||||
} catch (ApiClientException $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->json(['message' => 'Password reset invoked']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
|
||||||
#[Route('/api')]
|
#[Route('/api')]
|
||||||
|
#[IsGranted('ROLE_OAUTH2_API')]
|
||||||
class TravelController extends AbstractController
|
class TravelController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Api;
|
||||||
|
|
||||||
|
use App\BusProNet\ApiClient;
|
||||||
|
use App\BusProNet\Exception\ApiClientException;
|
||||||
|
use App\BusProNet\Model\Notification;
|
||||||
|
use App\BusProNet\Model\PersonalData;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Security\Crypt;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
#[Route('/api')]
|
||||||
|
#[IsGranted('ROLE_OAUTH2_EMAIL')]
|
||||||
|
class UserinfoController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ApiClient $apiClient, private readonly Crypt $crypt)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/userinfo', name: 'api_userinfo', methods: ['GET'])]
|
||||||
|
public function index(): JsonResponse
|
||||||
|
{
|
||||||
|
// basic scopes applicable to all authenticated users
|
||||||
|
$scopes = ['email'];
|
||||||
|
|
||||||
|
// extend scopes depending on granted permissions
|
||||||
|
if ($this->isGranted('ROLE_OAUTH2_PROFILE')) {
|
||||||
|
$scopes[] = 'profile';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->getUser();
|
||||||
|
$email = $user->getEmail();
|
||||||
|
$password = $this->crypt->decrypt($user->getPassword());
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = $this->apiClient->getPersonalData($email, $password);
|
||||||
|
|
||||||
|
if ($data instanceof Notification) {
|
||||||
|
return new JsonResponse(['message' => $data->message, 'code' => $data->code], Response::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract userdata for resulting claims
|
||||||
|
$userData = $this->getClaims($data, $scopes);
|
||||||
|
|
||||||
|
return $this->json($userData);
|
||||||
|
} catch (ApiClientException $e) {
|
||||||
|
return new JsonResponse(['message' => $e->getMessage()], Response::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClaims(PersonalData $data, array $scopes): array
|
||||||
|
{
|
||||||
|
// get all available claims
|
||||||
|
$allClaims = $data->getClaims();
|
||||||
|
$keys = array_keys($allClaims);
|
||||||
|
$claims = [];
|
||||||
|
|
||||||
|
// filter claims by provided scopes
|
||||||
|
foreach ($scopes as $scope) {
|
||||||
|
if (false === in_array($scope, $keys)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$claims[$scope] = $allClaims[$scope];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $claims;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
|
|
||||||
use function Symfony\Component\String\u;
|
use function Symfony\Component\String\u;
|
||||||
|
|
||||||
class DownloadController extends AbstractController
|
class DownloadController extends AbstractController
|
||||||
@@ -42,7 +43,7 @@ class DownloadController extends AbstractController
|
|||||||
requirements: ['id' => '\d+'],
|
requirements: ['id' => '\d+'],
|
||||||
defaults: ['fileType' => 'invoice']
|
defaults: ['fileType' => 'invoice']
|
||||||
)]
|
)]
|
||||||
#[IsGranted("ROLE_USER")]
|
#[IsGranted('ROLE_USER')]
|
||||||
public function documents(int $id, string $fileType): Response
|
public function documents(int $id, string $fileType): Response
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class EditController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/bookings/{id}/edit', name: 'app_booking_edit', requirements: ['id' => '\d+'])]
|
#[Route('/bookings/{id}/edit', name: 'app_booking_edit', requirements: ['id' => '\d+'])]
|
||||||
#[IsGranted("ROLE_USER")]
|
#[IsGranted('ROLE_USER')]
|
||||||
public function edit(int $id, Request $request): Response
|
public function edit(int $id, Request $request): Response
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class IndexController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/bookings', name: 'app_bookings')]
|
#[Route('/bookings', name: 'app_bookings')]
|
||||||
#[IsGranted("ROLE_USER")]
|
#[IsGranted('ROLE_USER')]
|
||||||
public function index(Request $request): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\BusProNet\ApiClient;
|
use App\BusProNet\ApiClient;
|
||||||
use App\BusProNet\Exception\ApiClientException;
|
use App\BusProNet\Exception\ApiClientException;
|
||||||
use App\BusProNet\Exception\ResponseParserException;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
@@ -38,7 +37,7 @@ class ResetPasswordController extends AbstractController
|
|||||||
new Email([
|
new Email([
|
||||||
'mode' => 'strict',
|
'mode' => 'strict',
|
||||||
'message' => 'Bitte eine gültige E-Mail Adresse angeben',
|
'message' => 'Bitte eine gültige E-Mail Adresse angeben',
|
||||||
])
|
]),
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
->getForm()
|
->getForm()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
@@ -12,7 +13,7 @@ class SecurityController extends AbstractController
|
|||||||
{
|
{
|
||||||
#[Route('/', name: 'app_login')]
|
#[Route('/', name: 'app_login')]
|
||||||
#[IsGranted('PUBLIC_ACCESS')]
|
#[IsGranted('PUBLIC_ACCESS')]
|
||||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
public function login(AuthenticationUtils $authenticationUtils, Request $request): Response
|
||||||
{
|
{
|
||||||
if (null !== $this->getUser()) {
|
if (null !== $this->getUser()) {
|
||||||
return $this->redirectToRoute('app_personal_data');
|
return $this->redirectToRoute('app_personal_data');
|
||||||
@@ -21,6 +22,15 @@ class SecurityController extends AbstractController
|
|||||||
$error = $authenticationUtils->getLastAuthenticationError();
|
$error = $authenticationUtils->getLastAuthenticationError();
|
||||||
$lastUsername = $authenticationUtils->getLastUsername();
|
$lastUsername = $authenticationUtils->getLastUsername();
|
||||||
|
|
||||||
|
// flag this request in the session as external OAuth2 request if
|
||||||
|
// applicable to immediately logout the current user after successful
|
||||||
|
// authentication (see App\EventListener\AuthorizationCodeListener).
|
||||||
|
$session = $request->getSession();
|
||||||
|
$targetPath = $session->get('_security.main.target_path');
|
||||||
|
if (str_contains($targetPath, '/authorize')) {
|
||||||
|
$session->set('_oauth2', true);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('security/login.html.twig', [
|
return $this->render('security/login.html.twig', [
|
||||||
'last_username' => $lastUsername,
|
'last_username' => $lastUsername,
|
||||||
'error' => $error,
|
'error' => $error,
|
||||||
@@ -30,5 +40,6 @@ class SecurityController extends AbstractController
|
|||||||
#[Route('/logout', name: 'app_logout')]
|
#[Route('/logout', name: 'app_logout')]
|
||||||
#[IsGranted('ROLE_USER')]
|
#[IsGranted('ROLE_USER')]
|
||||||
public function logout(): void
|
public function logout(): void
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventListener;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use League\Bundle\OAuth2ServerBundle\Event\AuthorizationRequestResolveEvent;
|
||||||
|
use League\Bundle\OAuth2ServerBundle\OAuth2Events;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
|
||||||
|
#[AsEventListener(event: OAuth2Events::AUTHORIZATION_REQUEST_RESOLVE, method: 'onAuthorizationRequestResolve')]
|
||||||
|
class AuthorizationCodeListener
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UrlGeneratorInterface $urlGenerator,
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
private readonly LoggerInterface $authenticationLogger,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Approves or denies authorization by checking for a current user session and redirects anonymous
|
||||||
|
* users to login page. We purposely don't check consents since these are irrelevant for our use case.
|
||||||
|
* We do check though if the current user is allowed to login to the requesting client.
|
||||||
|
*/
|
||||||
|
public function onAuthorizationRequestResolve(AuthorizationRequestResolveEvent $event): void
|
||||||
|
{
|
||||||
|
$request = $this->requestStack->getMainRequest();
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (null === $user) {
|
||||||
|
$response = new RedirectResponse(
|
||||||
|
$this->urlGenerator->generate('app_login', $request->query->all()),
|
||||||
|
Response::HTTP_TEMPORARY_REDIRECT
|
||||||
|
);
|
||||||
|
$event->setResponse($response);
|
||||||
|
$this->authenticationLogger->info('Authorization request without session');
|
||||||
|
} else {
|
||||||
|
$event->resolveAuthorization(AuthorizationRequestResolveEvent::AUTHORIZATION_APPROVED);
|
||||||
|
// in case this authorization request has been flagged as external in login controller,
|
||||||
|
// immediately logout the current user (see App\Controller\Core\Security\LoginController).
|
||||||
|
if (true === $request->getSession()->get('_oauth2', false)) {
|
||||||
|
$this->security->logout(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,13 +39,13 @@ class ParticipantType extends AbstractType
|
|||||||
'label' => 'Vorname',
|
'label' => 'Vorname',
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('lastName', TextType::class, [
|
->add('lastName', TextType::class, [
|
||||||
'label' => 'Nachname',
|
'label' => 'Nachname',
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('dateOfBirth', BirthdayType::class, [
|
->add('dateOfBirth', BirthdayType::class, [
|
||||||
'label' => 'Geburtsdatum',
|
'label' => 'Geburtsdatum',
|
||||||
@@ -54,7 +54,7 @@ class ParticipantType extends AbstractType
|
|||||||
'input' => 'datetime_immutable',
|
'input' => 'datetime_immutable',
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('gender', ChoiceType::class, [
|
->add('gender', ChoiceType::class, [
|
||||||
'label' => 'Geschlecht',
|
'label' => 'Geschlecht',
|
||||||
@@ -68,7 +68,7 @@ class ParticipantType extends AbstractType
|
|||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
'style' => false === $personalDataMutable ? 'pointer-events: none' : null,
|
'style' => false === $personalDataMutable ? 'pointer-events: none' : null,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('nationality', CountryType::class, [
|
->add('nationality', CountryType::class, [
|
||||||
'label' => 'Nationalität',
|
'label' => 'Nationalität',
|
||||||
@@ -77,21 +77,21 @@ class ParticipantType extends AbstractType
|
|||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
'style' => false === $personalDataMutable ? 'pointer-events: none' : null,
|
'style' => false === $personalDataMutable ? 'pointer-events: none' : null,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('email', EmailType::class, [
|
->add('email', EmailType::class, [
|
||||||
'label' => 'E-Mail',
|
'label' => 'E-Mail',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('mobile', TextType::class, [
|
->add('mobile', TextType::class, [
|
||||||
'label' => 'Telefon (mobil)',
|
'label' => 'Telefon (mobil)',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $personalDataMutable,
|
'readonly' => false === $personalDataMutable,
|
||||||
]
|
],
|
||||||
])
|
])
|
||||||
->add('height', ChoiceType::class, [
|
->add('height', ChoiceType::class, [
|
||||||
'label' => 'Körpergröße',
|
'label' => 'Körpergröße',
|
||||||
@@ -144,7 +144,8 @@ class ParticipantType extends AbstractType
|
|||||||
return $service->label;
|
return $service->label;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf('%s (%s €)',
|
return sprintf(
|
||||||
|
'%s (%s €)',
|
||||||
$service->label,
|
$service->label,
|
||||||
number_format($price, 2, ',', '.')
|
number_format($price, 2, ',', '.')
|
||||||
);
|
);
|
||||||
@@ -189,8 +190,8 @@ class ParticipantType extends AbstractType
|
|||||||
// remove choices only available in booking data and
|
// remove choices only available in booking data and
|
||||||
// which are not mapped to the current participant
|
// which are not mapped to the current participant
|
||||||
if (
|
if (
|
||||||
Service::SOURCE_TRAVEL === $service->source ||
|
Service::SOURCE_TRAVEL === $service->source
|
||||||
Service::CATEGORY_ADDITIONAL !== $service->category
|
|| Service::CATEGORY_ADDITIONAL !== $service->category
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -293,7 +294,8 @@ class ParticipantType extends AbstractType
|
|||||||
return $pickupLabel;
|
return $pickupLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf('%s %s€',
|
return sprintf(
|
||||||
|
'%s %s€',
|
||||||
$pickupLabel,
|
$pickupLabel,
|
||||||
number_format($price, 2, ',', '.')
|
number_format($price, 2, ',', '.')
|
||||||
);
|
);
|
||||||
@@ -308,7 +310,7 @@ class ParticipantType extends AbstractType
|
|||||||
},
|
},
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'readonly' => false === $options['pickups_mutable'],
|
'readonly' => false === $options['pickups_mutable'],
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
|
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
|
||||||
@@ -331,8 +333,9 @@ class ParticipantType extends AbstractType
|
|||||||
// forcibly select mandatory services that potentially have been disabled in PRE_SET_DATA
|
// forcibly select mandatory services that potentially have been disabled in PRE_SET_DATA
|
||||||
$mandatoryServices = array_filter($options['selectable_services'], function (Service $service) use ($form) {
|
$mandatoryServices = array_filter($options['selectable_services'], function (Service $service) use ($form) {
|
||||||
$participantIndex = $form->getData()->index;
|
$participantIndex = $form->getData()->index;
|
||||||
return true === $service->mandatory ||
|
|
||||||
(Service::SOURCE_BOOKING === $service->source && in_array($participantIndex, $service->mapping));
|
return true === $service->mandatory
|
||||||
|
|| (Service::SOURCE_BOOKING === $service->source && in_array($participantIndex, $service->mapping));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (0 < count($mandatoryServices)) {
|
if (0 < count($mandatoryServices)) {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ namespace App\Form;
|
|||||||
|
|
||||||
use App\BusProNet\Form\CountryType;
|
use App\BusProNet\Form\CountryType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Security;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
|
||||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
|
||||||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
|
||||||
|
|
||||||
class ApiKeyAuthenticator extends AbstractAuthenticator
|
|
||||||
{
|
|
||||||
public function __construct(private readonly array $apiKeys)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supports(Request $request): ?bool
|
|
||||||
{
|
|
||||||
return $request->headers->has('X-BPN-API-KEY');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function authenticate(Request $request): Passport
|
|
||||||
{
|
|
||||||
$apiKey = $request->headers->get('X-BPN-API-KEY');
|
|
||||||
|
|
||||||
if (null === $apiKey) {
|
|
||||||
throw new CustomUserMessageAuthenticationException('No API key provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === in_array($apiKey, $this->apiKeys)) {
|
|
||||||
throw new CustomUserMessageAuthenticationException('Invalid API key');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SelfValidatingPassport(
|
|
||||||
new UserBadge($apiKey, function (string $userIdentifier) use ($apiKey): ?UserInterface {
|
|
||||||
return new ApiUser($apiKey);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'message' => 'Authentication missing or failed',
|
|
||||||
];
|
|
||||||
|
|
||||||
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Security;
|
|
||||||
|
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
|
||||||
|
|
||||||
class ApiUser implements UserInterface
|
|
||||||
{
|
|
||||||
public function __construct(private readonly string $apiKey)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRoles(): array
|
|
||||||
{
|
|
||||||
return ['ROLE_USER', 'ROLE_API'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function eraseCredentials(): void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserIdentifier(): string
|
|
||||||
{
|
|
||||||
return $this->apiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,9 +22,12 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
|||||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||||
|
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||||
|
|
||||||
class BpnAuthenticator extends AbstractLoginFormAuthenticator implements AuthenticationEntryPointInterface
|
class BpnAuthenticator extends AbstractLoginFormAuthenticator implements AuthenticationEntryPointInterface
|
||||||
{
|
{
|
||||||
|
use TargetPathTrait;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly UrlGeneratorInterface $urlGenerator,
|
private readonly UrlGeneratorInterface $urlGenerator,
|
||||||
private readonly ApiClient $apiClient,
|
private readonly ApiClient $apiClient,
|
||||||
@@ -60,7 +63,7 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
|||||||
$csrfToken = $request->request->getString('_csrf_token');
|
$csrfToken = $request->request->getString('_csrf_token');
|
||||||
|
|
||||||
return new SelfValidatingPassport(
|
return new SelfValidatingPassport(
|
||||||
new UserBadge($email, function () use ($email, $password, $response, $request) {
|
new UserBadge($email, function () use ($email, $password, $response) {
|
||||||
return $this->createOrUpdateLocalUser($email, $password, $response->personId, $response->addressId);
|
return $this->createOrUpdateLocalUser($email, $password, $response->personId, $response->addressId);
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
@@ -106,6 +109,10 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
|||||||
'email' => $token->getUserIdentifier(),
|
'email' => $token->getUserIdentifier(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
|
||||||
|
return new RedirectResponse($targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
return new RedirectResponse($this->urlGenerator->generate('app_personal_data'));
|
return new RedirectResponse($this->urlGenerator->generate('app_personal_data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,18 @@
|
|||||||
"migrations/.gitignore"
|
"migrations/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"friendsofphp/php-cs-fixer": {
|
||||||
|
"version": "3.75",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.0",
|
||||||
|
"ref": "be2103eb4a20942e28a6dd87736669b757132435"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".php-cs-fixer.dist.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
"knplabs/knp-menu-bundle": {
|
"knplabs/knp-menu-bundle": {
|
||||||
"version": "v3.4.2"
|
"version": "v3.4.2"
|
||||||
},
|
},
|
||||||
@@ -42,6 +54,31 @@
|
|||||||
"var/storage/.gitignore"
|
"var/storage/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"league/oauth2-server-bundle": {
|
||||||
|
"version": "0.11",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "0.11",
|
||||||
|
"ref": "80320e8e61b51f6965b83a7df1cc9d40bcc3fb78"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/league_oauth2_server.yaml",
|
||||||
|
"config/routes/league_oauth2_server.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nyholm/psr7": {
|
||||||
|
"version": "1.8",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "4a8c0345442dcca1d8a2c65633dcf0285dd5a5a2"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/nyholm_psr7.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"phpunit/phpunit": {
|
"phpunit/phpunit": {
|
||||||
"version": "9.6",
|
"version": "9.6",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Tests\BusProNet\DataLoader;
|
namespace App\Tests\BusProNet\DataLoader;
|
||||||
|
|
||||||
use App\BusProNet\XmlLoader\HotelLoader;
|
|
||||||
use App\BusProNet\Model\Hotel;
|
use App\BusProNet\Model\Hotel;
|
||||||
|
use App\BusProNet\XmlLoader\HotelLoader;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class HotelDataLoaderTest extends TestCase
|
class HotelDataLoaderTest extends TestCase
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Tests\BusProNet\DataLoader;
|
namespace App\Tests\BusProNet\DataLoader;
|
||||||
|
|
||||||
use App\BusProNet\XmlLoader\PickupLoader;
|
|
||||||
use App\BusProNet\Model\Pickup;
|
use App\BusProNet\Model\Pickup;
|
||||||
|
use App\BusProNet\XmlLoader\PickupLoader;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class PickupDataLoaderTest extends TestCase
|
class PickupDataLoaderTest extends TestCase
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Tests\BusProNet\DataLoader;
|
namespace App\Tests\BusProNet\DataLoader;
|
||||||
|
|
||||||
use App\BusProNet\XmlLoader\TravelLoader;
|
|
||||||
use App\BusProNet\Model\Travel;
|
use App\BusProNet\Model\Travel;
|
||||||
|
use App\BusProNet\XmlLoader\TravelLoader;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class TravelDataLoaderTest extends TestCase
|
class TravelDataLoaderTest extends TestCase
|
||||||
|
|||||||
Reference in New Issue
Block a user