Compare commits

...

24 Commits

Author SHA1 Message Date
Charles-Edouard 400aeedb8f Merge pull request 'SSO' (#1) from SSO into main
Reviewed-on: #1
2025-07-29 16:46:45 +02:00
Charles 023bf29e0b add client scope 2025-07-04 15:20:42 +02:00
Charles 8a404d371e Refactor 2025-07-04 11:17:26 +02:00
Charles b56ac18229 Refactor 2025-07-04 11:16:42 +02:00
Charles 2bfbc3a75b Refactor 2025-07-04 11:14:04 +02:00
Charles 903013e306 Refactor( logout subscriber) 2025-07-04 11:13:42 +02:00
Charles 974c4a0cc3 Refactor( logout button) 2025-07-04 11:12:38 +02:00
Charles b3c75ba21f Refactor( delete userTab ) 2025-07-04 11:11:18 +02:00
Charles 9e77511702 Refactor( delete mercure controller ) 2025-07-04 11:09:12 +02:00
Charles 8efc8d53c4 Refactor( delete command ) 2025-07-04 10:55:24 +02:00
Charles 08024b301b Refactor 2025-07-04 10:23:49 +02:00
Charles f238a41205 Revoke access tokens 2025-07-04 10:20:51 +02:00
Charles 97da159794 ÇA FONCTIONNE 2025-07-04 10:05:08 +02:00
Charles 1b188ec833 ÇA FONCTIONNE 2025-07-04 10:04:59 +02:00
Charles 33a33c8152 token 2025-06-11 15:33:32 +02:00
Charles e011d36743 refacto 2025-05-22 11:09:11 +02:00
Charles cc5bb633ec update user info send 2025-05-22 11:08:44 +02:00
Charles f95b4c9df1 custom token 2025-05-22 11:07:44 +02:00
Charles 789d3aaaf9 ça fonctionne 2025-05-21 14:29:14 +02:00
Charles 58e71d9e80 modify env call 2025-04-23 16:40:58 +02:00
Charles b19b6a2988 Add scope modification in the token 2025-04-23 16:26:54 +02:00
Charles f41c34b750 change consent page template 2025-04-23 15:44:37 +02:00
Charles ecfad09771 .idea files 2025-04-23 11:05:52 +02:00
Charles 93f786bc7e set up decline CGU function 2025-04-23 10:34:45 +02:00
48 changed files with 7674 additions and 321 deletions

10
.env
View File

@ -52,3 +52,13 @@ OAUTH_ENCRYPTION_KEY=f1b7c279f7992205a0df45e295d07066
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###
###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
MERCURE_URL=https://example.com/.well-known/mercure
# The public URL of the Mercure hub, used by the browser to connect
MERCURE_PUBLIC_URL=https://example.com/.well-known/mercure
# The secret used to sign the JWTs
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
###< symfony/mercure-bundle ###

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

15
.idea/Easy_solution.iml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mercure" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mercure-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/firebase/php-jwt" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="framework" type="frameworkType"/>
<xs:complexType name="commandType">
<xs:all>
<xs:element type="xs:string" name="name" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="params" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="help" minOccurs="0" maxOccurs="1"/>
<xs:element type="optionsBeforeType" name="optionsBefore" minOccurs="0" maxOccurs="1"/>
</xs:all>
</xs:complexType>
<xs:complexType name="frameworkType">
<xs:sequence>
<xs:element type="xs:string" name="extraData" minOccurs="0" maxOccurs="1"/>
<xs:element type="commandType" name="command" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="xs:string" name="help" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute type="xs:string" name="invoke" use="required"/>
<xs:attribute type="xs:string" name="alias" use="required"/>
<xs:attribute type="xs:boolean" name="enabled" use="required"/>
<xs:attribute type="xs:integer" name="version" use="required"/>
<xs:attribute type="xs:string" name="frameworkId" use="optional"/>
</xs:complexType>
<xs:complexType name="optionsBeforeType">
<xs:sequence>
<xs:element type="optionType" name="option" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="optionType">
<xs:sequence>
<xs:element type="xs:string" name="help" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute type="xs:string" name="shortcut" use="optional"/>
<xs:attribute name="pattern" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="space"/>
<xs:enumeration value="equals"/>
<xs:enumeration value="unknown"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:schema>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Easy_solution.iml" filepath="$PROJECT_DIR$/.idea/Easy_solution.iml" />
</modules>
</component>
</project>

181
.idea/php.xml Normal file
View File

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/form" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/symfony/intl" />
<path value="$PROJECT_DIR$/vendor/paragonie/random_compat" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/monolog-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
<path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-toggle-password" />
<path value="$PROJECT_DIR$/vendor/symfony/messenger" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/league/oauth2-server" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/league/event" />
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/vendor/league/uri" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/league/oauth2-server-bundle" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/league/uri-interfaces" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/nelmio/cors-bundle" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-middleware" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/psr/link" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-handler" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/nyholm/psr7" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/egulias/email-validator" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/lcobucci/jwt" />
<path value="$PROJECT_DIR$/vendor/lcobucci/clock" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php83" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/mime" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-icu" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/monolog-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/asset-mapper" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-messenger" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/monolog/monolog" />
<path value="$PROJECT_DIR$/vendor/symfony/expression-language" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/mailer" />
<path value="$PROJECT_DIR$/vendor/symfony/phpunit-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/symfony/web-link" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/web-profiler-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/debug-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
<path value="$PROJECT_DIR$/vendor/defuse/php-encryption" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-turbo" />
<path value="$PROJECT_DIR$/vendor/symfony/stimulus-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-icons" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/symfony/notifier" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/symfony/mercure" />
<path value="$PROJECT_DIR$/vendor/symfony/mercure-bundle" />
<path value="$PROJECT_DIR$/vendor/firebase/php-jwt" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml.dist" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

10
.idea/phpunit.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/tests" />
</list>
</option>
</component>
</project>

7
.idea/symfony2.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Symfony2PluginSettings">
<option name="pluginEnabled" value="true" />
<option name="lastServiceGeneratorLanguage" value="yaml" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -7,10 +7,12 @@
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-openssl": "*",
"doctrine/dbal": "^3",
"doctrine/doctrine-bundle": "^2.14",
"doctrine/doctrine-migrations-bundle": "^3.4",
"doctrine/orm": "^3.3",
"firebase/php-jwt": "^6.11",
"league/oauth2-server-bundle": "^0.11.0",
"nelmio/cors-bundle": "^2.5",
"phpdocumentor/reflection-docblock": "^5.6",
@ -27,6 +29,7 @@
"symfony/http-client": "7.2.*",
"symfony/intl": "7.2.*",
"symfony/mailer": "7.2.*",
"symfony/mercure-bundle": "^0.3.9",
"symfony/mime": "7.2.*",
"symfony/monolog-bundle": "^3.0",
"symfony/notifier": "7.2.*",

737
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,4 +17,5 @@ return [
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
];

30
config/jwt/private.key Normal file
View File

@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIQ5VGud/OnzsCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECH23Z4CwP/AxBIIEyCUA/sYfp1Vo
x+pBPHs8EH5zW+oeCCqihANPGcYXHp3SusSQEA6jyEQz1GHEQF7nt5SxxqYTfe8D
ovU2TsspUlycUcZhQQmtM6n2f375G9L2fvIhnOMvD34YvYQ0yhgkToS3h+pORpqb
9abJbqkyTHSHOD4utSEaUnFapAQnKPWgFzYSjt4w6pVfX2AUZCXisC3QlcUh4782
S62jIfqggMcDU2mfkBBUHE7z1syC5eqYwWEs2P1S1fvSYjpFv7L8LlkZY+6w/qgf
IH1ht0A+rs6cQKILXW9yySqRfuumaGJtqQvlql4M7tb7zvDynVgN3/GQxm6oQmFB
pFlNWINN7WmKqhRjnJBpKl5L1+s2EWpxBiHXXQLdznqzKyx3tPuLoeVh2S96NEy6
f9380eDhNBYKdyUeZP+IBdwtV6U6vbwhtBQ53T9BQ6ykbKpRUm1fI4Y6AcVYW903
KCkFR3j6pF4My41AMgcBSJ+GON/PEzIgIIDlYnM+7DVhlentY71Bhf9la7YSaFtx
0r8d/CKdgGwPB2+QAEANOo0Z73RmovNRyadvZTcJqEgRC9R+DRbQHNYXN44TEhIU
MZaFLTTZHEmWs0f4kf+UUo7RjUi5HdNCjuXErvKgVhtqMofeKc1p6fnNbBCrFYzs
Knbwn+u2aLcI7LNLXaSWMOOGrbBw3g5FWObCxZ/FlNABp4yE+YT5YYeCLcemS/8n
ZD0l3b2wkLK7dze0DB3TKv+wZoBHtl1s3RYm9UDjB8ejHSzzRMpLK514+Q8Qthx9
IexC6hTzIG3wyjst98spb/6VQqDyA4TvMzW+pAA+9JgXyp7auHiU217nx5rSpdTe
hWCyRgaBOP48W/ZoBjVxx81/voKixfvBZbYJSciw7K4iNRMjPXNg2BvCZZLZ9Ubd
zLVCE15bzdGDZJis/VLawEFHZ1Urku9HfT4FhLxAweEc5VdnbdjEXIXqgf99VaK2
V6xXWF9EQ1pVaavR+jzm0ZHs5Ltwnc095+Y9flmvzm+eD00Ftm1IyrfInMl6f8Sv
gkxe1lxo6wbTE12CodhkH0woNd+y4K82ZSRzaTg6cUb+YTDKNAfoAnctghPe5+PX
3j1ITAQeBFjVXckDrm8jeqF/gbcQYJwFjY0/wtl84gN/Ffs8DSTt1FYwsxgGew43
L0KsVt/1Rd4Xb4czBRk7uUpR0U2R7iZ0yIrnGSh657tywpcn2thsJY4X6QjNRWNw
yDg5Yrw10tIsLYJLJuxAauGjXbk23lelwnwn8EtMYPI2XtzGSS3b7uGXjTu2pncQ
YVFbI/DxrRi3tyF7JLSC1mfNxikMfY6cPp489djDWC0iQVcltrHiHDkmMrloptE7
tTUvPfBRNAbonMaXR+pSSQBlDkUMk6uNpk9EYIlLGg2WggOYnikEKsLoX19Mjg5M
ZuX7fu2ETHJjvHBetMnQobtqKEGf5RJn+mdKBzia9ZAcjxhKRqYUCK8Qoi3mjvJa
x+5idCj2etNJA5idmH50NSXSFbO69Au+LCj1zSikN6h+YgOjqwF0TPQ7Qw4qQQkH
4NwYi6S4wCI13LyLVN1XTh9nn99Y/WnORyyegmAfQif1vbcPcfU6mY3VGK5tX2Yl
NhLAyGw7m2xDDmIefpRi+w==
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -1,30 +0,0 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIf5Dm9gHr5xICAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECAmIm56CqfdaBIIEyIeYoi/NL/yC
EM3P0ZEesYY08FoEjaAonqDAFB2w8caOfSc2quZSij7zaDAjU5ehg7X/3kWqypUW
F1bhMn5v3Lad6XOgXeTigYaLZBXshazuqnbqs9xBU00MPkwetl1CT4ATUzvEPPq1
LOQ2FfEUfBZAR51PVYvKJwwctzYW42PNh8940XqskAs6kfWQ3qZPlnCRjQDfuKcI
YhKeJz6cCYt5JHqBGC5mRiFkRqDe10gks0/yUCmds9MpKPLYKP9MrUs8QcxWpakf
ypUMPM2mSik4U+D7Gdq7CqulqDPLm+8drKgUeq6wTgWwodpGnUe5FeG+cx4hxH5x
fAKuP6gscvYk3p5ir138TVW4uEFehkBw7yHpqUqSjN5jIlmX634siYoNhWP6Ek32
nl+gGUlITWSC6cewiqpL7IfQr+DDXpTPcN5Lu9+6rmh4P0XGE+J2a/tdXeK3xdMZ
3MyFItGNIxklL+yuvRBJ2o4D5JRDhiSwmXdFR5WOJYN0SRKYkEnmjAAHnhK6eWLP
bsD0tdUilZvyGmcPMXkM3HjfAQKqDldi8rmdqe8IJHpCKZ5twryy/eb1EArofN0U
H9XIs/pdJldg2HTJVIen5iAONqMSB4LyxMUfXWnvg/qPazvgYZvKbTXyp0iVhJ77
UcWyIO6xx/3BvnuO/eCgelu+eNQqI9UOMhVr7X0gGpXb88g+Gu8scrlWDVTM6o9P
j1rOuAYaZX/69jGOmH3QqUkILcwkfevSuNUBgLOilh/mabR+tOfM2o6/8Z1Zd1PS
nplYzvDs/Pib71PEF64DVCRlG2QioqV2MT7gOenShyZhZS4+oxJ3nWf9Z9n5jwDJ
XSqkHRD8CDZZS0f1FOET1c+GZtoyfxGHdGyLM8shA6IRnxBoAKWVW8vdTfyPJFcy
vq9gPvlrfyuREcRKeSnySzkAQbYLVH631tLcsbsd5yfyYS0o6BY1mNDi1j83Xfmp
Or2a137ZKKTfRtSJybj+QOiBXMR8uJnR9HPjRQn8DrMYzcgv/kw48AIUy2+4sZIw
cPxrtKFyhWIYSnHil4Ri/cENZApbbo1UQc4ktyjuyxyI20gx21vfaHWbx6GpUNwG
H551tioa5P4cte4syOxMi89KRdCYKGgUTibhEfMODD6rA/l3W9PGINqOGu3Z2SxN
4HGV4oJx6Nvw1vTq2phSVo3Qp9JyvO3FFE8oA/b+ElL0PoYjsLsNiKBxggPvZgKp
nTOFYYUMQl1+6CUf2iSR0b8VLhRM5o9N+Xwp2D2SEjWZvMhIKQxxmwVd3aSn8ebQ
CSW0lGFjltwc5FEvw1aNmhx7K1gSCMcBnSEx53ghLU5xCA+50g5brM1joZn3sQ2c
WsY/dLNtfqlTnKSeVQKyLEZj6Xl9/KYyVg3yYt43HuSz3FAt6ekGloA9rVR3v7tZ
m6tBWyOqQG4tnp8+V/2CMGnpq2VQABqxwZxpm5u+tbfSlonwrXpiy3/fgsYZJE02
kVwNb4FPClKjyVZGRrWfITaS4Gxm+hDdfKVipunIzS+7MADwwYyibPVXyuCw1NTg
pt89WCTt0csNNGcpd1m/rrH5J080fEHGXWpeobbTbeiYsK0+qeYQXEeMvE7lyFIP
SzDJqo7z76xQc3dcBX8MNQ==
-----END ENCRYPTED PRIVATE KEY-----

9
config/jwt/public.key Normal file
View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwLRhFcrWQWeuGhgrFclT
c08RTX52kl9XMXatgJi/mfa/o7lbZbhQfNzrMXVZFDTs97YTZXotZVwL1pI7yGtv
bjbPNuhs+lvOSmWribIacYx47EqKeYhQ29rOYx7fnr+SvK10QZoFnT58tduQVrER
79b+lcbqKMKNeI6zZeVdBrhstuI/PtXnM4kMHThaTz3iLbWkoDyl06VIMFVsvHpc
HaPJsBH1I45M9l4gTfshknY3Dz5ESVd41XTH2c/PSa6geNUn5kpslCuxKv3DnmWE
h+V87ASCIM7tKE6bc0eFQLKPQo52/TUWfDa8nFbeIrbsQJwq5VhYK21TANNFFL3g
GwIDAQAB
-----END PUBLIC KEY-----

View File

@ -1,9 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwZehUUiAV9vMER+tCpKB
lLr53bCV2z34r7Qg4gojciR1n7J6esptuLo/JMIYsU1EFmH8dIJ9guc/IufW7hDj
iwB2l75brVJ+EG8RRN2R4/IlUXz01a56W+TgLGyVM9iNegfwrNQjEEr3WNnz878h
0KQDsG3+kndh60pIuIiiEqG/yoO9C4enyL5XEDcsyKebb5PeCSTewgGm+DY7vj1F
FSZMXxEnOQP/Symz8st1KS7DbbYma/OCkrAsZ+iHC1Ozis0D28uxcdyrPZi36bkV
MKNST42uV86CTEan2yaHaynRUFNHC+bZjA+izOALtg3CyguOqrmL1LL5ID8Q1K8f
jQIDAQAB
-----END PUBLIC KEY-----

View File

@ -5,7 +5,7 @@ league_oauth2_server:
encryption_key: '%env(resolve:OAUTH_ENCRYPTION_KEY)%'
access_token_ttl: PT3H # 3 hours
refresh_token_ttl: P1M # 1 month
auth_code_ttl: PT10M # 10 minutes
auth_code_ttl: PT3H # 10 minutes
require_code_challenge_for_public_clients: false
resource_server:
public_key: '%env(resolve:OAUTH_PUBLIC_KEY)%'

View File

@ -0,0 +1,8 @@
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: '*'

View File

@ -18,6 +18,17 @@ security:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
oauth_userinfo:
pattern: ^/oauth2/userinfo
stateless: true
oauth2: true
provider: app_user_provider
oauth_revoke:
pattern: ^/oauth2/revoke_tokens
stateless: true
auth_token:
pattern: ^/token
stateless: true
api:
pattern: ^/oauth/api
security: true
@ -32,9 +43,9 @@ security:
enable_csrf: true
default_target_path: app_index
use_referer: true
logout:
path: app_logout
target: app_login
# logout:
# path: app_logout
# target: app_login
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
@ -46,7 +57,9 @@ security:
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/sso_logout, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/token, roles: PUBLIC_ACCESS }
- { path: ^/oauth2/revoke_tokens, roles: PUBLIC_ACCESS }
- { path: ^/oauth2/token, roles: PUBLIC_ACCESS }
- { path: ^/token, roles: PUBLIC_ACCESS }
- { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED }

View File

@ -22,6 +22,12 @@ services:
App\EventSubscriber\:
resource: '../src/EventSubscriber/'
tags: ['kernel.event_subscriber']
App\EventSubscriber\ScopeResolveListener:
tags:
- { name: kernel.event_listener, event: league.oauth2_server.event.scope_resolve, method: onScopeResolve }
League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface:
class: App\Repository\AccessTokenRepository
decorates: 'League\Bundle\OAuth2ServerBundle\Repository\AccessTokenRepository'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View File

@ -0,0 +1,38 @@
<?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 Version20250514091443 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'
ALTER TABLE "user" ADD uuid UUID NOT NULL
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE "user" DROP uuid
SQL);
}
}

View File

@ -0,0 +1,38 @@
<?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 Version20250514093759 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'
ALTER TABLE "user" DROP uuid
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE "user" ADD uuid UUID NOT NULL
SQL);
}
}

View File

@ -0,0 +1,33 @@
<?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 Version20250521124056 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
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
}
}

View File

@ -0,0 +1,47 @@
<?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 Version20250626124556 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 user_tab (id SERIAL NOT NULL, users_id INT NOT NULL, tab_open INT NOT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_98F5228767B3B43D ON user_tab (users_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab ADD CONSTRAINT FK_98F5228767B3B43D FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab DROP CONSTRAINT FK_98F5228767B3B43D
SQL);
$this->addSql(<<<'SQL'
DROP TABLE user_tab
SQL);
}
}

View File

@ -0,0 +1,38 @@
<?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 Version20250626134726 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'
ALTER TABLE user_tab RENAME COLUMN tab_open TO ip_address
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab RENAME COLUMN ip_address TO tab_open
SQL);
}
}

View File

@ -0,0 +1,38 @@
<?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 Version20250626135125 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'
ALTER TABLE user_tab ALTER ip_address TYPE VARCHAR(255)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab ALTER ip_address TYPE INT
SQL);
}
}

View File

@ -0,0 +1,38 @@
<?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 Version20250626145913 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'
ALTER TABLE user_tab ADD tab_id VARCHAR(255) DEFAULT NULL
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab DROP tab_id
SQL);
}
}

View File

@ -0,0 +1,47 @@
<?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 Version20250627073903 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 subscriptions (id SERIAL NOT NULL, users_id INT NOT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_4778A0167B3B43D ON subscriptions (users_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE subscriptions ADD CONSTRAINT FK_4778A0167B3B43D FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE subscriptions DROP CONSTRAINT FK_4778A0167B3B43D
SQL);
$this->addSql(<<<'SQL'
DROP TABLE subscriptions
SQL);
}
}

View File

@ -0,0 +1,53 @@
<?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 Version20250704091028 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'
DROP SEQUENCE user_tab_id_seq CASCADE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab DROP CONSTRAINT fk_98f5228767b3b43d
SQL);
$this->addSql(<<<'SQL'
DROP TABLE user_tab
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE SCHEMA public
SQL);
$this->addSql(<<<'SQL'
CREATE SEQUENCE user_tab_id_seq INCREMENT BY 1 MINVALUE 1 START 1
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE user_tab (id SERIAL NOT NULL, users_id INT NOT NULL, ip_address VARCHAR(255) NOT NULL, tab_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX idx_98f5228767b3b43d ON user_tab (users_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_tab ADD CONSTRAINT fk_98f5228767b3b43d FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
}

View File

@ -2,15 +2,19 @@
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class IndexController extends AbstractController
{
#[Route('/', name: 'app_index')]
public function index(): Response
public function index(Request $request, LoggerInterface $logger): Response
{
$logger->info("SESSION ID: " . $request->getSession()->getId());
return $this->render('index/index.html.twig', [
'controller_name' => 'IndexController',
]);

View File

@ -2,41 +2,74 @@
namespace App\Controller;
use App\Entity\User;
use App\Service\AccessTokenService;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class OAuth2Controller extends AbstractController
{
#[Route('/oauth/api/user', name: 'app_oauth_api_user')]
public function oauthApiUser(): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
return new JsonResponse([
'message' => 'Authentification réussie !',
'email' => $user->getEmail(),
'name' => $user->getName()
]);
}
#[Route('/oauth2/userinfo', name: 'userinfo', methods: ['GET'])]
public function userinfo(): JsonResponse
public function userinfo(Request $request): JsonResponse
{
$user = $this->getUser();
// dd($user);
if (!$user) {
return new JsonResponse(['error' => 'Unauthorized'], 401);
}
return new JsonResponse([
'sub' => $user->getId(),
'username' => $user->getName(),
'id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'roles' => $user->getRoles(),
'surname' => $user->getSurname(),
]);
}
}
#[Route('.well-known/jwks.json', name: 'app_jwks', methods: ['GET'])]
public function jwks(): Response
{
// Load the public key from the filesystem and use OpenSSL to parse it.
$kernelDirectory = $this->getParameter('kernel.project_dir');
$publicKey = openssl_pkey_get_public(file_get_contents($kernelDirectory . '/config/jwt/public.key'));
$details = openssl_pkey_get_details($publicKey);
$jwks = [
'keys' => [
[
'kty' => 'RSA',
'alg' => 'RS256',
'use' => 'sig',
'n' => strtr(rtrim(base64_encode($details['rsa']['n']), '='), '+/', '-_'),
'e' => strtr(rtrim(base64_encode($details['rsa']['e']), '='), '+/', '-_'),
],
],
];
return $this->json($jwks);
}
#[Route(path: '/oauth2/revoke_tokens', name: 'revoke_tokens', methods: ['POST'])]
public function revokeTokens(Security $security, Request $request, AccessTokenService $accessTokenService, LoggerInterface $logger): Response{
//Check if the user have valid access token
$data = json_decode($request->getContent(), true);
$userIdentifier = $data['user_identifier'];
if (!$userIdentifier) {
return new JsonResponse(["ERROR" => "User identifier is required"], Response::HTTP_BAD_REQUEST);
}
$accessTokenService->revokeTokens($userIdentifier);
$logger->info("Revoke tokens successfully");
return new JsonResponse(["SUCCESS" => "Tokens revoked successfully"], Response::HTTP_OK);
}
}

View File

@ -2,7 +2,12 @@
namespace App\Controller;
use App\Service\AccessTokenService;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
@ -35,10 +40,23 @@ class SecurityController extends AbstractController
]);
}
#[Route(path: '/logout', name: 'app_logout')]
public function logout(): void
#[Route(path: '/sso_logout', name: 'sso_logout')]
public function ssoLogout(RequestStack $stack, LoggerInterface $logger, AccessTokenService $accessTokenService, Security $security): Response
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
// Invalidate the session and revoke tokens
try{
if( $stack->getSession()->invalidate()){
$accessTokenService->revokeTokens($security->getUser()->getUserIdentifier());
$security->logout(false);
$logger->info("Logout successfully");
// Redirect back to the client (or to a “you are logged out” page)
return $this->redirect('/');
}
}catch (\Exception $e){
$logger->log(LogLevel::ERROR, 'Error invalidating session: ' . $e->getMessage());
}
// If something goes wrong, redirect to the index page
return $this->redirectToRoute('app_index');
}
#[Route(path: '/consent', name: 'app_consent')]
@ -59,4 +77,5 @@ class SecurityController extends AbstractController
// For GET requests, just show the consent form
return $this->render('security/consent.html.twig');
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Entity;
use DateTimeImmutable;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
final class AccessToken implements AccessTokenEntityInterface
{
use AccessTokenTrait;
use EntityTrait;
use TokenEntityTrait;
private function convertToJWT()
{
$this->initJwtConfiguration();
return $this->jwtConfiguration->builder()
->permittedFor($this->getClient()->getIdentifier())
->identifiedBy($this->getIdentifier())
->issuedAt(new DateTimeImmutable())
->canOnlyBeUsedAfter(new DateTimeImmutable())
->expiresAt($this->getExpiryDateTime())
->relatedTo((string) $this->getUserIdentifier())
->withClaim('scopes', $this->getScopes())
->withClaim('email', $this->getUserIdentifier())
->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey());
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Entity;
use App\Repository\SubscriptionsRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: SubscriptionsRepository::class)]
class Subscriptions
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'subscriptions')]
#[ORM\JoinColumn(nullable: false)]
private ?User $users = null;
public function getId(): ?int
{
return $this->id;
}
public function getUsers(): ?User
{
return $this->users;
}
public function setUsers(?User $users): static
{
$this->users = $users;
return $this;
}
}

View File

@ -3,6 +3,8 @@
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
@ -53,6 +55,17 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(options: ['default' => false])]
private ?bool $isDeleted = null;
/**
* @var Collection<int, Subscriptions>
*/
#[ORM\OneToMany(targetEntity: Subscriptions::class, mappedBy: 'users')]
private Collection $subscriptions;
public function __construct()
{
$this->subscriptions = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
@ -219,4 +232,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
{
return (string) $this->getId();
}
/**
* @return Collection<int, Subscriptions>
*/
public function getSubscriptions(): Collection
{
return $this->subscriptions;
}
public function addSubscription(Subscriptions $subscription): static
{
if (!$this->subscriptions->contains($subscription)) {
$this->subscriptions->add($subscription);
$subscription->setUsers($this);
}
return $this;
}
public function removeSubscription(Subscriptions $subscription): static
{
if ($this->subscriptions->removeElement($subscription)) {
// set the owning side to null (unless already changed)
if ($subscription->getUsers() === $this) {
$subscription->setUsers(null);
}
}
return $this;
}
}

View File

@ -65,4 +65,4 @@ class AuthorizationCodeSubscriber implements EventSubscriberInterface
'league.oauth2_server.event.authorization_request_resolve' => 'onLeagueOauth2ServerEventAuthorizationRequestResolve',
];
}
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\EventSubscriber;
use App\Service\ClientService;
use Doctrine\ORM\EntityManagerInterface;
use League\Bundle\OAuth2ServerBundle\Event\ScopeResolveEvent;
use League\Bundle\OAuth2ServerBundle\Repository\ScopeRepository;
use League\Bundle\OAuth2ServerBundle\ValueObject\Scope;
use League\Bundle\OAuth2ServerBundle\Model\Client;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class ScopeResolveListener implements EventSubscriberInterface
{
private ClientRepositoryInterface $clientRepository;
private LoggerInterface $logger;
private ClientService $clientService;
private EntityManagerInterface $entityManager;
public function __construct(ClientRepositoryInterface $clientRepository, LoggerInterface $logger, ClientService $clientService, EntityManagerInterface $entityManager)
{
$this->logger = $logger;
// Inject the client repository
$this->clientRepository = $clientRepository;
$this->clientService = $clientService;
$this->entityManager = $entityManager;
}
public function onScopeResolve(ScopeResolveEvent $event): void
{
// Get the client ID from the event
$client = $event->getClient();
$clientIdentifier = $client->getIdentifier();
// Get the requested scopes from the event
$requestedScopes = $event->getScopes();
// Prepare our final scopes collection
$finalScopes = [];
// Add default scopes that everyone gets
$defaultScopes = ['email', 'profile', 'openid'];
foreach ($defaultScopes as $scope) {
$finalScopes[] = new Scope($scope);
}
$clientEntity = $this->entityManager->getRepository(Client::class)->findOneBy(['identifier' => $clientIdentifier]);
$finalScopes[] = new Scope('apps:'. $clientEntity->getName());
// Add client-specific scopes based on client identifier or name
// switch ($clientIdentifier) {
// case 'a712b3caede9588372b2a83947fae53e':
// $finalScopes[] = new Scope('apps:easyexploit');
// break;
// case '14bbb1b1692ac3a45159e263e3e7ec67':
// $finalScopes[] = new Scope('apps:client');
// break;
// case 'EasyMonithor':
// $finalScopes[] = new Scope('apps:easymonithor');
// break;
// case 'EasyCheck':
// $finalScopes[] = new Scope('apps:easycheck');
// break;
// // Add more cases as needed for other applications
// }
// // If the client is an admin client, add admin scopes
// if (str_contains($client->getName(), 'Admin')) {
// $finalScopes[] = new Scope('apps:manage');
// $finalScopes[] = new Scope('orgs:manage');
// $finalScopes[] = new Scope('users:manage');
// }
// Set the resolved scopes
$event->setScopes(...$finalScopes);
}
public static function getSubscribedEvents(): array
{
return [
ScopeResolveEvent::class => 'onScopeResolve',
];
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Repository;
use League\Bundle\OAuth2ServerBundle\Repository\AccessTokenRepository as BaseAccessTokenRepository;
use App\Entity\AccessToken as AccessTokenEntity;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
final class AccessTokenRepository implements AccessTokenRepositoryInterface
{
private AccessTokenRepositoryInterface $baseAccessTokenRepository;
public function __construct(AccessTokenRepositoryInterface $baseAccessTokenRepository)
{
$this->baseAccessTokenRepository = $baseAccessTokenRepository;
}
/**
* {@inheritdoc}
*/
public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null): AccessTokenEntityInterface
{
/** @var int|string|null $userIdentifier */
$accessToken = new AccessTokenEntity();
$accessToken->setClient($clientEntity);
$accessToken->setUserIdentifier($userIdentifier);
foreach ($scopes as $scope) {
$accessToken->addScope($scope);
}
return $accessToken;
}
/**
* {@inheritdoc}
*/
public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void
{
$this->baseAccessTokenRepository->persistNewAccessToken($accessTokenEntity);
}
/**
* @param string $tokenId
*/
public function revokeAccessToken($tokenId): void
{
$this->baseAccessTokenRepository->revokeAccessToken($tokenId);
}
/**
* @param string $tokenId
*/
public function isAccessTokenRevoked($tokenId): bool
{
return $this->baseAccessTokenRepository->isAccessTokenRevoked($tokenId);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Subscriptions;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Subscriptions>
*/
class SubscriptionsRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Subscriptions::class);
}
// /**
// * @return Subscriptions[] Returns an array of Subscriptions objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('s')
// ->andWhere('s.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('s.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Subscriptions
// {
// return $this->createQueryBuilder('s')
// ->andWhere('s.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use League\Bundle\OAuth2ServerBundle\Model\AccessToken;
class AccessTokenService
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function revokeTokens(String $userIdentifier): void {
$accessTokens = $this->entityManager->getRepository(AccessToken::class)->findBy(['userIdentifier' => $userIdentifier, 'revoked' => false]);
foreach($accessTokens as $accessToken) {
$accessToken->revoke();
$this->entityManager->persist($accessToken);
$this->entityManager->flush();
}
}
public function getUserFromToken(string $token)
{
$data = json_decode(base64_decode(strtr($token, '-_', '+/')), true);
if (isset($data['user_identifier'])) {
return $data['user_identifier'];
}
return null;
}
}

View File

@ -50,4 +50,13 @@ class CguUserService
$cguUser->setIsAccepted(true);
$this->entityManager->flush();
}
//Function can only be called if the user has already accepted the CGU
public function declineCgu(UserInterface $user, Cgu $cgu): void
{
$cguUser = $this->entityManager->getRepository(CguUser::class)->findOneBy(['users' => $user, 'cgu' => $cgu]);
$cguUser->setIsAccepted(false);
$this->entityManager->flush();
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Service;
use League\Bundle\OAuth2ServerBundle\Model\Client;
use Doctrine\ORM\EntityManagerInterface;
class ClientService{
/**
* Retrieves a Client entity by its identifier.
*
* @param string $identifier The identifier of the client.
* @param EntityManagerInterface $entityManager The entity manager to use for database operations.
* @return Client|null The Client entity or null if not found.
*/
public function getClientIdentifier(String $identifier, EntityManagerInterface $entityManager): Client
{
return $entityManager->getRepository(Client::class)->findOneBy(['identifier' => $identifier]);
}
}

View File

@ -1,4 +1,13 @@
{
"doctrine/deprecations": {
"version": "1.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
}
},
"doctrine/doctrine-bundle": {
"version": "2.14",
"recipe": {
@ -181,6 +190,18 @@
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/mercure-bundle": {
"version": "0.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "0.3",
"ref": "528285147494380298f8f991ee8c47abebaf79db"
},
"files": [
"config/packages/mercure.yaml"
]
},
"symfony/messenger": {
"version": "7.2",
"recipe": {

View File

@ -16,6 +16,21 @@
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
<script>
const eventSource = new EventSource("{{ mercure('http://solutions-easy.moi/connect?userId='~ app.user.userIdentifier)|raw }}");
if (!sessionStorage.getItem('tabId')) {
sessionStorage.setItem('tabId', self.crypto.randomUUID ? self.crypto.randomUUID() : Math.random().toString(36).substr(2, 9));
}
const tabId = sessionStorage.getItem('tabId');
console.log('Tab ID:', tabId);
fetch('/register-tab', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ tabId: tabId })
});
</script>
{% endblock %}
</head>
<body>
@ -23,7 +38,7 @@
{{ include('elements/navbar.html.twig')}}
{% block body %}
{% endblock %}
<div>

View File

@ -63,7 +63,7 @@
<i >{{ ux_icon('bi:menu-up', {height: '20px', width: '20px'}) }}</i>
</div>
<div class="col-9">
<a href="http://api.solutions-easy.moi"> tyet </a>
<a href="http://client.solutions-easy.moi"> Client </a>
{# <select class="form-control">
<option>Exploit</options>
<option>Monithor</options>
@ -72,8 +72,8 @@
</select> #}
</div>
</div>
<a class="dropdown-item" style="padding-left: 8px;" href="#">
<i class="me-2">{{ ux_icon('material-symbols:logout', {height: '20px', width: '20px'}) }}</i>
<a class="dropdown-item" style="padding-left: 8px;" href="{{ path('sso_logout') }}">
<i class="me-2">{{ ux_icon('material-symbols:logout', {height: '20px', width: '20px'}) }}</i>
Deconnexion
</a>
</div>

View File

@ -6,7 +6,7 @@
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('sso_logout') }}">Logout</a>
</div>
{% endif %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'base.html.twig' %}
{% extends 'publicBase.html.twig' %}
{% block title %} Consent {% endblock %}