diff --git a/composer.json b/composer.json
index be710c0..f2b1fb1 100644
--- a/composer.json
+++ b/composer.json
@@ -11,6 +11,7 @@
"doctrine/doctrine-bundle": "^2.13",
"doctrine/doctrine-migrations-bundle": "^3.4",
"doctrine/orm": "^3.3",
+ "knpuniversity/oauth2-client-bundle": "^2.18",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.1",
"symfony/asset": "7.2.*",
@@ -56,7 +57,8 @@
},
"autoload": {
"psr-4": {
- "App\\": "src/"
+ "App\\": "src/",
+ "Sudalys\\OAuth2\\Client\\": "libs/sudalys/oauth2-client/src"
}
},
"autoload-dev": {
diff --git a/composer.lock b/composer.lock
index 344fd99..529b63c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "eaa614d3c00b5654c4cc228dcad4b8c8",
+ "content-hash": "a2e7a54b8d79cc97db7598ed43170f6c",
"packages": [
{
"name": "composer/semver",
@@ -1368,6 +1368,455 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "7.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+ "guzzlehttp/psr7": "^2.7.0",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2 || ^3.0"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-curl": "*",
+ "guzzle/client-integration-tests": "3.0.2",
+ "php-http/message-factory": "^1.1",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+ "psr/log": "^1.1 || ^2.0 || ^3.0"
+ },
+ "suggest": {
+ "ext-curl": "Required for CURL handler support",
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "psr-18",
+ "psr-7",
+ "rest",
+ "web service"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/guzzle/issues",
+ "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-24T11:22:20+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/promises/issues",
+ "source": "https://github.com/guzzle/promises/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-17T10:06:22+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.7.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-18T11:15:46+00:00"
+ },
+ {
+ "name": "knpuniversity/oauth2-client-bundle",
+ "version": "v2.18.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/knpuniversity/oauth2-client-bundle.git",
+ "reference": "c38ca88a70aae3694ca346a41b13b9a8f6e33ed4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/c38ca88a70aae3694ca346a41b13b9a8f6e33ed4",
+ "reference": "c38ca88a70aae3694ca346a41b13b9a8f6e33ed4",
+ "shasum": ""
+ },
+ "require": {
+ "league/oauth2-client": "^2.0",
+ "php": ">=8.1",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/framework-bundle": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^5.4|^6.0|^7.0",
+ "symfony/routing": "^5.4|^6.0|^7.0"
+ },
+ "require-dev": {
+ "league/oauth2-facebook": "^1.1|^2.0",
+ "symfony/phpunit-bridge": "^5.4|^6.0|^7.0",
+ "symfony/security-guard": "^5.4",
+ "symfony/yaml": "^5.4|^6.0|^7.0"
+ },
+ "suggest": {
+ "symfony/security-guard": "For integration with Symfony's Guard Security layer"
+ },
+ "type": "symfony-bundle",
+ "autoload": {
+ "psr-4": {
+ "KnpU\\OAuth2ClientBundle\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ryan Weaver",
+ "email": "ryan@symfonycasts.com"
+ }
+ ],
+ "description": "Integration with league/oauth2-client to provide services",
+ "homepage": "https://symfonycasts.com",
+ "keywords": [
+ "oauth",
+ "oauth2"
+ ],
+ "support": {
+ "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues",
+ "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.18.3"
+ },
+ "time": "2024-10-02T14:26:09+00:00"
+ },
+ {
+ "name": "league/oauth2-client",
+ "version": "2.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/oauth2-client.git",
+ "reference": "9df2924ca644736c835fc60466a3a60390d334f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/9df2924ca644736c835fc60466a3a60390d334f9",
+ "reference": "9df2924ca644736c835fc60466a3a60390d334f9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
+ "php": "^7.1 || >=8.0.0 <8.5.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.5",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
+ "squizlabs/php_codesniffer": "^3.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "League\\OAuth2\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alex Bilbie",
+ "email": "hello@alexbilbie.com",
+ "homepage": "http://www.alexbilbie.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Woody Gilk",
+ "homepage": "https://github.com/shadowhand",
+ "role": "Contributor"
+ }
+ ],
+ "description": "OAuth 2.0 Client Library",
+ "keywords": [
+ "Authentication",
+ "SSO",
+ "authorization",
+ "identity",
+ "idp",
+ "oauth",
+ "oauth2",
+ "single sign on"
+ ],
+ "support": {
+ "issues": "https://github.com/thephpleague/oauth2-client/issues",
+ "source": "https://github.com/thephpleague/oauth2-client/tree/2.8.1"
+ },
+ "time": "2025-02-26T04:37:30+00:00"
+ },
{
"name": "monolog/monolog",
"version": "3.8.1",
@@ -1893,6 +2342,166 @@
},
"time": "2019-01-08T18:20:26+00:00"
},
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "time": "2023-09-23T14:17:50+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/2.0"
+ },
+ "time": "2023-04-04T09:54:51+00:00"
+ },
{
"name": "psr/link",
"version": "2.0.1",
@@ -1999,6 +2608,50 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
{
"name": "symfony/asset",
"version": "v7.2.0",
diff --git a/config/bundles.php b/config/bundles.php
index 4e3a560..e9ad544 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -13,4 +13,5 @@ return [
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
+ KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
];
diff --git a/config/packages/knpu_oauth2_client.yaml b/config/packages/knpu_oauth2_client.yaml
new file mode 100644
index 0000000..3027e1c
--- /dev/null
+++ b/config/packages/knpu_oauth2_client.yaml
@@ -0,0 +1,12 @@
+knpu_oauth2_client:
+ clients:
+ sudalys:
+ type: generic
+ provider_class: Sudalys\OAuth2\Client\Provider\Sudalys
+ client_id: '%env(SSO_CLIENT_ID)%'
+ client_secret: '%env(SSO_CLIENT_SECRET)%'
+ redirect_route: app_home
+ redirect_params: {}
+ provider_options:
+ domain: 'https://portail.solutions-easy.moi'
+ use_state: false
\ No newline at end of file
diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 367af25..0ffd559 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -4,24 +4,34 @@ security:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
- users_in_memory: { memory: null }
+ sudalys:
+ entity:
+ class: App\Entity\User
+ property: email
+
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
- lazy: true
- provider: users_in_memory
-
- # activate different ways to authenticate
- # https://symfony.com/doc/current/security.html#the-firewall
-
- # https://symfony.com/doc/current/security/impersonating_user.html
- # switch_user: true
+ provider: sudalys
+ custom_authenticators:
+ - App\Security\OAuthAuthenticator
+ entry_point: App\Security\OAuthAuthenticator
+ logout:
+ path: app_logout
+ target: app_home
+ invalidate_session: true
+ clear_site_data:
+ - cookies
+ - storage
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
+ - { path: ^/dashboard, roles: ROLE_USER }
+ - { path: ^/user-info, roles: ROLE_USER }
+ - { path: ^/connect, roles: PUBLIC_ACCESS }
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
diff --git a/config/services.yaml b/config/services.yaml
index 2d6a76f..28fcd32 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -20,5 +20,12 @@ services:
- '../src/Entity/'
- '../src/Kernel.php'
+ # Register our custom OAuth user provider
+ knpu.oauth2.client.user_provider:
+ class: App\Security\OAuthUserProvider
+ arguments:
+ - '@doctrine.orm.entity_manager'
+ - '@knpu.oauth2.registry'
+
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
diff --git a/libs/sudalys/oauth2-client/.gitignore b/libs/sudalys/oauth2-client/.gitignore
new file mode 100644
index 0000000..25efb0f
--- /dev/null
+++ b/libs/sudalys/oauth2-client/.gitignore
@@ -0,0 +1,9 @@
+/build
+/log
+/vendor
+.*.cache
+composer.phar
+composer.lock
+infection.json
+infection.phar*
+phpunit.xml
\ No newline at end of file
diff --git a/libs/sudalys/oauth2-client/CHANGELOG.md b/libs/sudalys/oauth2-client/CHANGELOG.md
new file mode 100644
index 0000000..e9229f5
--- /dev/null
+++ b/libs/sudalys/oauth2-client/CHANGELOG.md
@@ -0,0 +1,78 @@
+# Changelog
+All notable changes to `oauth2-gitlab` will be documented in this file
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased]
+Nothing yet.
+
+## [3.4.0] - 2021-02-08
+### Added
+ - Compatibility with php-gitlab-api v10
+ - Test suite compatible with PHP8
+
+## [3.3.0] - 2020-02-10
+### Added
+ - Compatibility with php-gitlab-api v10
+
+## [3.2.0] - 2020-02-10
+### Changed
+ - Updated dependencies to those requiring up to date PHP versions
+
+### Removed
+ - Support for outdated and unsupported PHP versions (<7.2)
+
+## [3.1.2] - 2018-11-23
+### Changed
+ - Added conflict with `oauth2-client:2.4.0` due to [breaking change upstream](https://github.com/thephpleague/oauth2-client/issues/752) (#6)
+
+## [3.1.1] - 2018-10-01
+### Added
+ - PHP 7.2 and nightly added to test suite
+ - Infection testing added
+
+### Changed
+ - Test suite upgraded to PHPUnit 5/7 hybrid
+
+## [3.1.0] - 2017-11-01
+### Added
+ - Access scope support was implemented
+
+## [3.0.0] - 2017-05-31
+### Changed
+ - **Breaking**: Upgrade Gitlab API from v3 to v4
+ - Test suite upgraded from PHPUnit 4 to 5/6 hybrid
+
+## [2.0.0] - 2017-02-03
+### Added
+ - PHP 7.1 is now officially supported and tested
+
+### Changed
+ - **Breaking**: Upgrade league/oauth2-client to major version 2
+ - Included PHP-CS-Fixer
+
+### Removed
+ - PHP 5.5 is end of life and no longer supported
+
+## [1.1.0] - 2016-08-28
+### Added
+ - Added `getApiClient` method on `GitlabResourceOwner` to get an API connector
+
+## [1.0.0] - 2016-05-20
+### Changed
+ - Cleaned up everything after definitive testing for stable release
+
+## 1.0.0-alpha-1 - 2016-05-16
+### Added
+ - Original fork, feature complete
+
+[Unreleased]: https://github.com/omines/oauth2-gitlab/compare/3.4.0...master
+[3.4.0]: https://github.com/omines/oauth2-gitlab/compare/3.3.0...3.2.0
+[3.3.0]: https://github.com/omines/oauth2-gitlab/compare/3.2.0...3.3.0
+[3.2.0]: https://github.com/omines/oauth2-gitlab/compare/3.1.2...3.2.0
+[3.1.2]: https://github.com/omines/oauth2-gitlab/compare/3.1.1...3.1.2
+[3.1.1]: https://github.com/omines/oauth2-gitlab/compare/3.1.0...3.1.1
+[3.1.0]: https://github.com/omines/oauth2-gitlab/compare/3.0.0...3.1.0
+[3.0.0]: https://github.com/omines/oauth2-gitlab/compare/2.0.0...3.0.0
+[2.0.0]: https://github.com/omines/oauth2-gitlab/compare/1.1.0...2.0.0
+[1.1.0]: https://github.com/omines/oauth2-gitlab/compare/1.0.0...1.1.0
+[1.0.0]: https://github.com/omines/oauth2-gitlab/compare/1.0.0-alpha.1...1.0.0
diff --git a/libs/sudalys/oauth2-client/CONTRIBUTING.md b/libs/sudalys/oauth2-client/CONTRIBUTING.md
new file mode 100644
index 0000000..c3c5b21
--- /dev/null
+++ b/libs/sudalys/oauth2-client/CONTRIBUTING.md
@@ -0,0 +1,7 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+We accept contributions via Pull Requests on [Github](https://github.com/omines/oauth2-gitlab). Follow
+[good standards](http://www.phptherightway.com/), keep code coverage at 100%, and run `vendor/bin/php-cs-fixer fix`
+before committing.
diff --git a/libs/sudalys/oauth2-client/LICENSE b/libs/sudalys/oauth2-client/LICENSE
new file mode 100644
index 0000000..1cdd1f4
--- /dev/null
+++ b/libs/sudalys/oauth2-client/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2021 Omines Internetbureau B.V. / Steven Maguire
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/libs/sudalys/oauth2-client/README.md b/libs/sudalys/oauth2-client/README.md
new file mode 100644
index 0000000..557e395
--- /dev/null
+++ b/libs/sudalys/oauth2-client/README.md
@@ -0,0 +1,121 @@
+# GitLab Provider for OAuth 2.0 Client
+[](https://github.com/omines/oauth2-gitlab/releases)
+[](LICENSE.md)
+[](https://travis-ci.org/omines/oauth2-gitlab)
+[](https://scrutinizer-ci.com/g/omines/oauth2-gitlab/code-structure)
+[](https://scrutinizer-ci.com/g/omines/oauth2-gitlab)
+[](https://packagist.org/packages/omines/oauth2-gitlab)
+
+This package provides GitLab OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). GitLab 8.17 or later is required.
+
+## Installation
+
+To install, use composer:
+
+```
+composer require omines/oauth2-gitlab
+```
+
+## Usage
+
+Usage is similar to the basic OAuth client, using `\Omines\OAuth2\Client\Provider\Gitlab` as the provider.
+
+### Authorization Code Flow
+
+```php
+$provider = new \Omines\OAuth2\Client\Provider\Sudalys([
+ 'clientId' => '{gitlab-client-id}',
+ 'clientSecret' => '{gitlab-client-secret}',
+ 'redirectUri' => 'https://example.com/callback-url',
+ 'domain' => 'https://my.gitlab.example', // Optional base URL for self-hosted
+]);
+
+if (!isset($_GET['code'])) {
+
+ // If we don't have an authorization code then get one
+ $authUrl = $provider->getAuthorizationUrl();
+ $_SESSION['oauth2state'] = $provider->getState();
+ header('Location: '.$authUrl);
+ exit;
+
+// Check given state against previously stored one to mitigate CSRF attack
+} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
+
+ unset($_SESSION['oauth2state']);
+ exit('Invalid state');
+
+} else {
+
+ // Try to get an access token (using the authorization code grant)
+ $token = $provider->getAccessToken('authorization_code', [
+ 'code' => $_GET['code'],
+ ]);
+
+ // Optional: Now you have a token you can look up a users profile data
+ try {
+
+ // We got an access token, let's now get the user's details
+ $user = $provider->getResourceOwner($token);
+
+ // Use these details to create a new profile
+ printf('Hello %s!', $user->getName());
+
+ } catch (Exception $e) {
+
+ // Failed to get user details
+ exit('Oh dear...');
+ }
+
+ // Use this to interact with an API on the users behalf
+ echo $token->getToken();
+}
+```
+
+### Managing Scopes
+
+When creating your GitLab authorization URL, you can specify the state and scopes your application may authorize.
+
+```php
+$options = [
+ 'state' => 'OPTIONAL_CUSTOM_CONFIGURED_STATE',
+ 'scope' => ['read_user','openid'] // array or string
+];
+
+$authorizationUrl = $provider->getAuthorizationUrl($options);
+```
+If neither are defined, the provider will utilize internal defaults ```'api'```.
+
+
+### Performing API calls
+
+Install [`m4tthumphrey/php-gitlab-api`](https://packagist.org/packages/m4tthumphrey/php-gitlab-api) to interact with the
+Gitlab API after authentication. Either connect manually:
+
+```php
+$client = new \Gitlab\Client();
+$client->setUrl('https://my.gitlab.url/api/v4/');
+$client->authenticate($token->getToken(), \Gitlab\Client::AUTH_OAUTH_TOKEN);
+```
+Or call the `getApiClient` method on `GitlabResourceOwner` which does the same implicitly.
+
+## Testing
+
+```bash
+$ ./vendor/bin/phpunit
+```
+
+## Contributing
+
+Please see [CONTRIBUTING](https://github.com/omines/oauth2-gitlab/blob/master/CONTRIBUTING.md) for details.
+
+
+## Credits
+
+This code is a modified fork from the [official Github provider](https://github.com/thephpleague/oauth2-github) adapted
+for Gitlab use, so many credits go to [Steven Maguire](https://github.com/stevenmaguire).
+
+## Legal
+
+This software was developed for internal use at [Omines Full Service Internetbureau](https://www.omines.nl/)
+in Eindhoven, the Netherlands. It is shared with the general public under the permissive MIT license, without
+any guarantee of fitness for any particular purpose. Refer to the included `LICENSE` file for more details.
diff --git a/libs/sudalys/oauth2-client/composer.json b/libs/sudalys/oauth2-client/composer.json
new file mode 100644
index 0000000..1b7503c
--- /dev/null
+++ b/libs/sudalys/oauth2-client/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "sudalys/oauth2-sudalys",
+ "description": "GitLab OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "JB LOPEZ",
+ "email": "contact@jblopez.fr",
+ "homepage": "https://www.jblopez.fr"
+ }
+ ],
+ "keywords": [
+ "oauth",
+ "oauth2",
+ "client",
+ "authorization",
+ "authorisation",
+ "gitlab"
+ ],
+ "require": {
+ "php": ">=7.2",
+ "league/oauth2-client": "^2.4.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.0",
+ "guzzlehttp/psr7": "^1.6",
+ "http-interop/http-factory-guzzle": "^1.0",
+ "mockery/mockery": "^1.0",
+ "m4tthumphrey/php-gitlab-api": "^10.0|^11.0",
+ "php-http/guzzle7-adapter": "^0.1",
+ "phpunit/phpunit": "^8.0|^9.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Sudalys\\OAuth2Sudalys\\Client\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ }
+}
diff --git a/libs/sudalys/oauth2-client/src/Provider/Exception/SudalysIdentityProviderException.php b/libs/sudalys/oauth2-client/src/Provider/Exception/SudalysIdentityProviderException.php
new file mode 100644
index 0000000..9595ed7
--- /dev/null
+++ b/libs/sudalys/oauth2-client/src/Provider/Exception/SudalysIdentityProviderException.php
@@ -0,0 +1,57 @@
+
+ */
+class SudalysIdentityProviderException extends IdentityProviderException
+{
+ /**
+ * Creates client exception from response.
+ *
+ * @param mixed $data Parsed response data
+ */
+ public static function clientException(ResponseInterface $response, $data): IdentityProviderException
+ {
+ return static::fromResponse(
+ $response,
+ isset($data['message']) ? $data['message'] : $response->getReasonPhrase()
+ );
+ }
+
+ /**
+ * Creates oauth exception from response.
+ *
+ * @param ResponseInterface $response Response received from upstream
+ * @param array $data Parsed response data
+ */
+ public static function oauthException(ResponseInterface $response, $data): IdentityProviderException
+ {
+ return static::fromResponse(
+ $response,
+ isset($data['error']) ? $data['error'] : $response->getReasonPhrase()
+ );
+ }
+
+ /**
+ * Creates identity exception from response.
+ *
+ * @param ResponseInterface $response Response received from upstream
+ * @param string|null $message Parsed message
+ */
+ protected static function fromResponse(ResponseInterface $response, $message = null): IdentityProviderException
+ {
+ return new static($message, $response->getStatusCode(), (string) $response->getBody());
+ }
+}
diff --git a/libs/sudalys/oauth2-client/src/Provider/Sudalys.php b/libs/sudalys/oauth2-client/src/Provider/Sudalys.php
new file mode 100644
index 0000000..9a56c2b
--- /dev/null
+++ b/libs/sudalys/oauth2-client/src/Provider/Sudalys.php
@@ -0,0 +1,158 @@
+domain = $options['domain'];
+ }
+ parent::__construct($options, $collaborators);
+ }
+
+ /**
+ * Get authorization url to begin OAuth flow.
+ */
+ public function getBaseAuthorizationUrl(): string
+ {
+ return $this->domain . self::PATH_AUTHORIZE;
+ }
+
+ /**
+ * Requests resource owner details.
+ *
+ * @param AccessToken $token
+ * @return mixed
+ */
+ protected function fetchResourceOwnerDetails(AccessToken $token)
+ {
+ $url = $this->getResourceOwnerDetailsUrl($token);
+ $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token);
+ $response = $this->getParsedResponse($request);
+ if (false === is_array($response)) {
+ throw new UnexpectedValueException(
+ 'Invalid response received from Authorization Server. Expected JSON.'
+ );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Sends a request and returns the parsed response.
+ *
+ * @param RequestInterface $request
+ * @throws IdentityProviderException
+ * @return mixed
+ */
+ /* public function getParsedResponse(RequestInterface $request)
+ {
+ try {
+ $response = $this->getResponse($request);
+ } catch (BadResponseException $e) {
+ $response = $e->getResponse();
+ }
+
+ $parsed = $this->parseResponse($response);
+
+ echo "
----------------------------- PARSED RESPONSE -------------------------
";
+ print_r($parsed);
+
+ $this->checkResponse($response, $parsed);
+
+ return $parsed;
+ }*/
+
+ /**
+ * Get access token url to retrieve token.
+ */
+ public function getBaseAccessTokenUrl(array $params): string
+ {
+ return $this->domain . self::PATH_TOKEN;
+ }
+
+ /**
+ * Get provider url to fetch user details.
+ */
+ public function getResourceOwnerDetailsUrl(AccessToken $token): string
+ {
+ //DEBUG echo '
'.$this->domain . self::PATH_API_USER;
+ return $this->domain . self::PATH_API_USER;
+ }
+
+ /**
+ * Get the default scopes used by GitLab.
+ * Current scopes are 'api', 'read_user', 'openid'.
+ *
+ * This returns an array with 'api' scope as default.
+ */
+ protected function getDefaultScopes(): array
+ {
+ return [self::DEFAULT_SCOPE];
+ }
+
+ /**
+ * GitLab uses a space to separate scopes.
+ */
+ protected function getScopeSeparator(): string
+ {
+ return self::SCOPE_SEPARATOR;
+ }
+
+ /**
+ * Check a provider response for errors.
+ *
+ * @param ResponseInterface $response Parsed response data
+ * @throws IdentityProviderException
+ */
+ protected function checkResponse(ResponseInterface $response, $data)
+ {
+ if ($response->getStatusCode() >= 400) {
+ throw SudalysIdentityProviderException::clientException($response, $data);
+ } elseif (isset($data['error'])) {
+ throw SudalysIdentityProviderException::oauthException($response, $data);
+ }
+ }
+
+ /**
+ * Generate a user object from a successful user details request.
+ */
+ protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface
+ {
+ $user = new SudalysResourceOwner($response, $token);
+
+ return $user->setDomain($this->domain);
+ }
+}
diff --git a/libs/sudalys/oauth2-client/src/Provider/SudalysResourceOwner.php b/libs/sudalys/oauth2-client/src/Provider/SudalysResourceOwner.php
new file mode 100644
index 0000000..53ba32e
--- /dev/null
+++ b/libs/sudalys/oauth2-client/src/Provider/SudalysResourceOwner.php
@@ -0,0 +1,156 @@
+data = $response;
+ $this->token = $token;
+ }
+
+ /**
+ * Returns the identifier of the authorized resource owner.
+ */
+ public function getId(): int
+ {
+ return (int) $this->get('id');
+ }
+
+
+ public function getDomain(): string
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Retrieves ful token resource owner.
+ *
+ * @return string|null
+ */
+ public function getIdToken()
+ {
+ return $this->token;
+ }
+
+
+ /**
+ * @return $this
+ */
+ public function setDomain(string $domain): self
+ {
+ $this->domain = $domain;
+
+ return $this;
+ }
+
+ /**
+ * The full name of the owner.
+ */
+ public function getName(): string
+ {
+ return $this->get('name');
+ }
+
+ /**
+ * Username of the owner.
+ */
+ public function getUsername(): string
+ {
+ return $this->get('username');
+ }
+
+ /**
+ * Email address of the owner.
+ */
+ public function getEmail(): string
+ {
+ return $this->get('email');
+ }
+
+ /**
+ * URL to the user's avatar.
+ *
+ * @return string|null
+ */
+ public function getAvatarUrl(): string
+ {
+ return $this->get('avatar_url');
+ }
+
+ /**
+ * URL to the user's profile page.
+ */
+ public function getProfileUrl(): string
+ {
+ return $this->get('web_url');
+ }
+
+ public function getToken(): AccessToken
+ {
+ return $this->token;
+ }
+
+ /**
+ * Whether the user is active.
+ */
+ public function isActive(): bool
+ {
+ return 'active' === $this->get('state');
+ }
+
+ /**
+ * Whether the user is an admin.
+ */
+ public function isAdmin(): bool
+ {
+ return (bool) $this->get('is_admin', false);
+ }
+
+ /**
+ * Whether the user is external.
+ */
+ public function isExternal(): bool
+ {
+ return (bool) $this->get('external', true);
+ }
+
+ /**
+ * Return all of the owner details available as an array.
+ */
+ public function toArray(): array
+ {
+ return $this->data;
+ }
+
+ /**
+ * @param mixed|null $default
+ * @return mixed|null
+ */
+ protected function get(string $key, $default = null)
+ {
+ return isset($this->data[$key]) ? $this->data[$key] : $default;
+ }
+}
diff --git a/migrations/Version20250326093406.php b/migrations/Version20250326093406.php
new file mode 100644
index 0000000..b497276
--- /dev/null
+++ b/migrations/Version20250326093406.php
@@ -0,0 +1,50 @@
+addSql('CREATE TABLE "user" (id SERIAL NOT NULL, username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, roles JSON NOT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON "user" (username)');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)');
+ $this->addSql('CREATE TABLE messenger_messages (id BIGSERIAL NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
+ $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
+ $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
+ $this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'(DC2Type:datetime_immutable)\'');
+ $this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'(DC2Type:datetime_immutable)\'');
+ $this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'(DC2Type:datetime_immutable)\'');
+ $this->addSql('CREATE OR REPLACE FUNCTION notify_messenger_messages() RETURNS TRIGGER AS $$
+ BEGIN
+ PERFORM pg_notify(\'messenger_messages\', NEW.queue_name::text);
+ RETURN NEW;
+ END;
+ $$ LANGUAGE plpgsql;');
+ $this->addSql('DROP TRIGGER IF EXISTS notify_trigger ON messenger_messages;');
+ $this->addSql('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON messenger_messages FOR EACH ROW EXECUTE PROCEDURE notify_messenger_messages();');
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql('CREATE SCHEMA public');
+ $this->addSql('DROP TABLE "user"');
+ $this->addSql('DROP TABLE messenger_messages');
+ }
+}
diff --git a/src/Controller/ClientTestController.php b/src/Controller/ClientTestController.php
new file mode 100644
index 0000000..aeda58a
--- /dev/null
+++ b/src/Controller/ClientTestController.php
@@ -0,0 +1,82 @@
+ false, 'verify_host' => false]);
+
+ foreach ([$authorizeUrl, $tokenUrl, $userInfoUrl] as $url) {
+ try {
+ $response = $client->request('GET', $url, ['timeout' => 3]);
+ $status = $response->getStatusCode();
+ $urlTests[$url] = "Status: $status";
+ } catch (TransportExceptionInterface $e) {
+ $urlTests[$url] = "Error: " . $e->getMessage();
+ } catch (\Exception $e) {
+ $urlTests[$url] = "Exception: " . $e->getMessage();
+ }
+ }
+
+ // Test 2: Try to get a token with client credentials
+ $tokenTest = "Not tested";
+ try {
+ $guzzleClient = new Client([
+ 'verify' => false,
+ 'timeout' => 5,
+ ]);
+
+ $response = $guzzleClient->post($tokenUrl, [
+ 'form_params' => [
+ 'grant_type' => 'client_credentials',
+ 'client_id' => $clientId,
+ 'client_secret' => $clientSecret,
+ ],
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]);
+
+ $tokenTest = $response->getBody()->getContents();
+ } catch (RequestException $e) {
+ $response = $e->getResponse();
+ if ($response) {
+ $tokenTest = "Error: " . $response->getStatusCode() . " - " . $response->getBody()->getContents();
+ } else {
+ $tokenTest = "Error: " . $e->getMessage();
+ }
+ } catch (\Exception $e) {
+ $tokenTest = "Exception: " . $e->getMessage();
+ }
+
+ return $this->render('client_test/index.html.twig', [
+ 'client_id' => $clientId,
+ 'client_secret' => substr($clientSecret, 0, 3) . '***',
+ 'authorize_url' => $authorizeUrl,
+ 'token_url' => $tokenUrl,
+ 'userinfo_url' => $userInfoUrl,
+ 'url_tests' => $urlTests,
+ 'token_test' => $tokenTest,
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php
new file mode 100644
index 0000000..0c0c4c1
--- /dev/null
+++ b/src/Controller/DashboardController.php
@@ -0,0 +1,122 @@
+render('dashboard/home.html.twig', [
+ 'controller_name' => 'DashboardController',
+ ]);
+ }
+
+ #[Route('/dashboard', name: 'app_dashboard')]
+ public function index(): Response
+ {
+ $this->denyAccessUnlessGranted('ROLE_USER');
+
+ return $this->render('dashboard/index.html.twig', [
+ 'controller_name' => 'DashboardController',
+ ]);
+ }
+
+ #[Route('/user-info', name: 'app_user_info')]
+ public function userInfo(LoggerInterface $logger): Response
+ {
+ if (!$this->getUser()) {
+ throw new AccessDeniedException('You must be logged in to view this page');
+ }
+
+ $user = $this->getUser();
+ $logger->info('User info accessed', [
+ 'user' => $user->getUserIdentifier()
+ ]);
+
+ return $this->render('dashboard/user_info.html.twig', [
+ 'user' => $user
+ ]);
+ }
+
+ #[Route('/connect/sso', name: 'connect_sso_start')]
+ public function connectAction(ClientRegistry $clientRegistry, LoggerInterface $logger): Response
+ {
+ try {
+ $client = $clientRegistry->getClient('sudalys');
+
+ $logger->info('Redirecting to OAuth authorization endpoint');
+
+ // Don't pass any options - use defaults configured in knpu_oauth2_client.yaml
+ return $client->redirect();
+ } catch (\Exception $e) {
+ $logger->error('OAuth connection error: ' . $e->getMessage(), [
+ 'exception' => get_class($e),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine()
+ ]);
+
+ return new JsonResponse([
+ 'error' => 'OAuth connection failed',
+ 'message' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ 'environment' => [
+ 'SSO_CLIENT_ID' => $_ENV['SSO_CLIENT_ID'] ?? 'not set',
+ 'SSO_CLIENT_SECRET' => substr($_ENV['SSO_CLIENT_SECRET'] ?? 'not set', 0, 3) . '***',
+ 'SSO_AUTHORIZE_URL' => $_ENV['SSO_AUTHORIZE_URL'] ?? 'not set',
+ 'SSO_TOKEN_URL' => $_ENV['SSO_TOKEN_URL'] ?? 'not set',
+ 'SSO_USERINFO_URL' => $_ENV['SSO_USERINFO_URL'] ?? 'not set',
+ ]
+ ], 500);
+ }
+ }
+
+ #[Route('/connect/sso/check', name: 'connect_sso_check')]
+ public function connectCheckAction(LoggerInterface $logger): Response
+ {
+ $logger->info('OAuth callback called');
+ // This method will be intercepted by the OAuth authenticator
+ return new JsonResponse(['status' => 'This should not be displayed if OAuth is working properly']);
+ }
+
+ #[Route('/logout', name: 'app_logout')]
+ public function logout(): void
+ {
+ // This method can be empty - it will be intercepted by the logout key on your firewall
+ throw new \Exception('This should never be reached!');
+ }
+
+ #[Route('/oauth-debug', name: 'oauth_debug')]
+ public function oauthDebug(): Response
+ {
+ return new JsonResponse([
+ 'SSO_CLIENT_ID' => $_ENV['SSO_CLIENT_ID'] ?? 'not set',
+ 'SSO_CLIENT_SECRET' => substr($_ENV['SSO_CLIENT_SECRET'] ?? 'not set', 0, 3) . '***',
+ 'SSO_AUTHORIZE_URL' => $_ENV['SSO_AUTHORIZE_URL'] ?? 'not set',
+ 'SSO_TOKEN_URL' => $_ENV['SSO_TOKEN_URL'] ?? 'not set',
+ 'SSO_USERINFO_URL' => $_ENV['SSO_USERINFO_URL'] ?? 'not set',
+ ]);
+ }
+
+ #[Route('/login-status', name: 'app_login_status')]
+ public function loginStatus(): JsonResponse
+ {
+ $user = $this->getUser();
+
+ return new JsonResponse([
+ 'logged_in' => $user !== null,
+ 'user' => $user ? [
+ 'identifier' => $user->getUserIdentifier(),
+ 'roles' => $user->getRoles(),
+ ] : null,
+ ]);
+ }
+}
diff --git a/src/Entity/User.php b/src/Entity/User.php
new file mode 100644
index 0000000..68fe10f
--- /dev/null
+++ b/src/Entity/User.php
@@ -0,0 +1,109 @@
+id;
+ }
+
+ public function getUsername(): ?string
+ {
+ return $this->username;
+ }
+
+ public function setUsername(string $username): static
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ public function getPassword(): ?string
+ {
+ return $this->password;
+ }
+
+ public function setPassword(string $password): static
+ {
+ $this->password = $password;
+
+ return $this;
+ }
+
+ public function getEmail(): ?string
+ {
+ return $this->email;
+ }
+
+ public function setEmail(string $email): static
+ {
+ $this->email = $email;
+
+ return $this;
+ }
+
+ /**
+ * A visual identifier that represents this user.
+ *
+ * @see UserInterface
+ */
+ public function getUserIdentifier(): string
+ {
+ return (string) $this->email;
+ }
+
+ /**
+ * @see UserInterface
+ */
+ public function getRoles(): array
+ {
+ $roles = $this->roles;
+ // guarantee every user at least has ROLE_USER
+ $roles[] = 'ROLE_USER';
+
+ return array_unique($roles);
+ }
+
+ public function setRoles(array $roles): static
+ {
+ $this->roles = $roles;
+
+ return $this;
+ }
+
+ /**
+ * @see UserInterface
+ */
+ public function eraseCredentials(): void
+ {
+ // If you store any temporary, sensitive data on the user, clear it here
+ // $this->plainPassword = null;
+ }
+}
diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php
new file mode 100644
index 0000000..b29153b
--- /dev/null
+++ b/src/Repository/UserRepository.php
@@ -0,0 +1,43 @@
+
+ */
+class UserRepository extends ServiceEntityRepository
+{
+ public function __construct(ManagerRegistry $registry)
+ {
+ parent::__construct($registry, User::class);
+ }
+
+ // /**
+ // * @return User[] Returns an array of User objects
+ // */
+ // public function findByExampleField($value): array
+ // {
+ // return $this->createQueryBuilder('u')
+ // ->andWhere('u.exampleField = :val')
+ // ->setParameter('val', $value)
+ // ->orderBy('u.id', 'ASC')
+ // ->setMaxResults(10)
+ // ->getQuery()
+ // ->getResult()
+ // ;
+ // }
+
+ // public function findOneBySomeField($value): ?User
+ // {
+ // return $this->createQueryBuilder('u')
+ // ->andWhere('u.exampleField = :val')
+ // ->setParameter('val', $value)
+ // ->getQuery()
+ // ->getOneOrNullResult()
+ // ;
+ // }
+}
diff --git a/src/Security/OAuthAuthenticator.php b/src/Security/OAuthAuthenticator.php
new file mode 100644
index 0000000..4c91fcd
--- /dev/null
+++ b/src/Security/OAuthAuthenticator.php
@@ -0,0 +1,141 @@
+clientRegistry = $clientRegistry;
+ $this->router = $router;
+ $this->logger = $logger;
+ }
+
+ public function supports(Request $request): ?bool
+ {
+ return $request->attributes->get('_route') === 'connect_sso_check' && $request->query->has('code');
+ }
+
+ public function authenticate(Request $request): Passport
+ {
+ $client = $this->getSudalysClient();
+ $accessToken = $this->fetchAccessToken($client);
+
+ return new SelfValidatingPassport(
+ new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) {
+ $sudalysSsoUser = $this->getSudalysClient()->fetchUserFromToken($accessToken);
+
+ $email = $sudalysSsoUser->getEmail();
+
+ /*
+ * On regarde si l'utilisateur est deja connecté
+ */
+ $connectedUser = $this->em->getRepository(User::class)->findOneBy(['ssoToken' => $sudalysSsoUser->getIdToken()]);
+ if ($connectedUser) {
+ return $connectedUser;
+ }
+
+ // On reggarde si user existe en bdd
+ /** @var User $userInDatabase */
+ $user = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
+
+
+ /**
+ * A commenter si on ne veut pas creer l utilisateur s il n existe pas en bdd locale
+ * **/
+ if (!$user) {
+ $user = new User();
+ $user->setEmail($sudalysSsoUser->getEmail());
+ $user->setPassword("createAPasswordMethod");
+ $this->em->persist($user);
+ }
+
+ // On met a jour le token
+ $user->setSsoToken($sudalysSsoUser->getIdToken());
+ $this->em->flush();
+
+ return $user;
+ })
+ );
+ }
+
+ private function getUserIdentifier(ResourceOwnerInterface $resourceOwner): string
+ {
+ // Get the unique identifier from the resource owner
+ // This depends on your SSO server's response format
+ // For standard OAuth servers, this might be the 'sub' field
+ $data = $resourceOwner->toArray();
+
+ $this->logger->debug('Resource owner data', [
+ 'data' => $data
+ ]);
+
+ // Try to get a unique identifier from the data
+ if (isset($data['sub'])) {
+ return $data['sub'];
+ } elseif (isset($data['id'])) {
+ return $data['id'];
+ } elseif (isset($data['email'])) {
+ return $data['email'];
+ }
+
+ // Fallback to a hash of the entire data if no good identifier is found
+ return md5(json_encode($data));
+ }
+
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
+ {
+ $this->logger->info('OAuth authentication successful', [
+ 'user' => $token->getUserIdentifier()
+ ]);
+
+ return new RedirectResponse($this->router->generate('app_home'));
+ }
+
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
+ {
+ $this->logger->error('OAuth authentication failure', [
+ 'message' => $exception->getMessage(),
+ 'code' => $exception->getCode()
+ ]);
+
+ return new JsonResponse([
+ 'error' => 'Authentication failed',
+ 'message' => $exception->getMessage(),
+ 'query_params' => $request->query->all()
+ ], Response::HTTP_UNAUTHORIZED);
+ }
+
+ public function start(Request $request, AuthenticationException $authException = null): Response
+ {
+ return new RedirectResponse(
+ $this->router->generate('connect_sso_start'),
+ Response::HTTP_TEMPORARY_REDIRECT
+ );
+ }
+
+ private function getSudalysClient()
+ {
+ return $this->clientRegistry->getClient('sudalys');
+ }
+}
\ No newline at end of file
diff --git a/src/Security/OAuthUserProvider.php b/src/Security/OAuthUserProvider.php
new file mode 100644
index 0000000..3a0a02b
--- /dev/null
+++ b/src/Security/OAuthUserProvider.php
@@ -0,0 +1,76 @@
+entityManager = $entityManager;
+ $this->clientRegistry = $clientRegistry;
+ }
+
+ /**
+ * Loads a user from the given token.
+ */
+ public function loadUserByIdentifier(string $identifier): UserInterface
+ {
+ // Try to find an existing user with this identifier
+ $userRepository = $this->entityManager->getRepository(User::class);
+ $user = $userRepository->findOneBy(['username' => $identifier]);
+
+ // If the user doesn't exist, create one
+ if (!$user) {
+ $user = new User();
+ $user->setUsername($identifier);
+ $user->setPassword(''); // No password for OAuth users
+
+ // Try to get an email from the current client's token data if available
+ try {
+ $client = $this->clientRegistry->getClient('sso_server');
+ $accessToken = $client->getAccessToken();
+ if ($accessToken) {
+ $resourceOwner = $client->fetchUserFromToken($accessToken);
+ $userData = $resourceOwner->toArray();
+
+ // Set email if available in the response
+ if (isset($userData['email'])) {
+ $user->setEmail($userData['email']);
+ } else {
+ $user->setEmail($identifier . '@example.com');
+ }
+ } else {
+ $user->setEmail($identifier . '@example.com');
+ }
+ } catch (\Exception $e) {
+ // Fallback if we can't get the email
+ $user->setEmail($identifier . '@example.com');
+ }
+
+ $user->setRoles(['ROLE_USER']);
+
+ $this->entityManager->persist($user);
+ $this->entityManager->flush();
+ }
+
+ return $user;
+ }
+
+ /**
+ * Check if this provider supports the given user class.
+ */
+ public function supportsClass($class): bool
+ {
+ return $class === User::class || is_subclass_of($class, User::class);
+ }
+}
\ No newline at end of file
diff --git a/symfony.lock b/symfony.lock
index 4947424..0d4b7cd 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -26,6 +26,18 @@
"migrations/.gitignore"
]
},
+ "knpuniversity/oauth2-client-bundle": {
+ "version": "2.18",
+ "recipe": {
+ "repo": "github.com/symfony/recipes-contrib",
+ "branch": "main",
+ "version": "1.20",
+ "ref": "1ff300d8c030f55c99219cc55050b97a695af3f6"
+ },
+ "files": [
+ "config/packages/knpu_oauth2_client.yaml"
+ ]
+ },
"phpunit/phpunit": {
"version": "9.6",
"recipe": {
diff --git a/templates/client_test/index.html.twig b/templates/client_test/index.html.twig
new file mode 100644
index 0000000..2a55ee3
--- /dev/null
+++ b/templates/client_test/index.html.twig
@@ -0,0 +1,54 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}OAuth Client Test{% endblock %}
+
+{% block body %}
+
{{ result }}+
{{ token_test }}+
Your identifier: {{ app.user.userIdentifier }}
+Click the button below to login with SSO
+This page is only accessible to authenticated users with ROLE_USER
.
You are logged in as: {{ app.user.userIdentifier }}
+Roles: + {% for role in app.user.roles %} + {{ role }} + {% endfor %} +
+This is sensitive information that only authenticated users can see.
+Your SSO authentication has successfully granted you access to this protected resource.
+Identifier: | +{{ user.userIdentifier }} | +
---|---|
Username: | +{{ user.username }} | +
Email: | +{{ user.email }} | +
Roles: | +
+
|
+
{{ dump(user) }}+
{{ dump(app.session) }}+