diff --git a/.docker/oidc-server-mock/cert/docker.pfx b/.docker/oidc-server-mock/cert/docker.pfx new file mode 100644 index 0000000..36f6258 Binary files /dev/null and b/.docker/oidc-server-mock/cert/docker.pfx differ diff --git a/.env b/.env index 5aed2be..39d597a 100644 --- a/.env +++ b/.env @@ -33,6 +33,27 @@ MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!" CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###< nelmio/cors-bundle ### +# Set this to a non-empty value in .env.local to disable the OIDC service. +# DOCKER_OIDC_DISABLE="" + DEFAULT_LOCALE=en SITE_TITLE="RPA Process Overview" +DEFAULT_URI=https://rpa-process-overview.local.itkdev.dk/ + +# https://github.com/itk-dev/openid-connect-bundle +# "admin" open id connect configuration variables (values provided by the OIDC IdP) +# For production, these must be overridden in .env.local. +ADMIN_OIDC_ALLOW_HTTP=true +ADMIN_OIDC_METADATA_URL=http://idp.rpa-process-overview.local.itkdev.dk/.well-known/openid-configuration +ADMIN_OIDC_CLIENT_ID=client-id +ADMIN_OIDC_CLIENT_SECRET=client-secret +ADMIN_OIDC_REDIRECT_URI=https://rpa-process-overview.local.itkdev.dk/ +ADMIN_OIDC_LEEWAY=30 +ADMIN_OIDC_ROLE_MAP='{ + "overview-manager": ["ROLE_OVERVIEW_MANAGER"] +}' + +# cli redirect url +OIDC_CLI_LOGIN_ROUTE=app_default +###< itk-dev/openid-connect-bundle ### diff --git a/CHANGELOG.md b/CHANGELOG.md index cce2bb5..64e9dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-10](https://github.com/itk-dev/rpa-process-overview/pull/10) + Added OIDC login * [PR-9](https://github.com/itk-dev/rpa-process-overview/pull/9) Tailwind classes in twig and svelte files * [PR-8](https://github.com/itk-dev/rpa-process-overview/pull/8) diff --git a/README.md b/README.md index 5ec8d99..00ae682 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,42 @@ curl -H "Origin: http://127.0.0.1:3000/ProcessOverview?page=3" \ ## User management -We have a number of commands for managing users. Run +[Symfony supports OpenID Connect](https://symfony.com/doc/current/security/access_token.html#using-openid-connect-oidc), +but our IdP does not play well with that. Therefore, we use our own battle-tested [OpenId Connect +Bundle](https://github.com/itk-dev/openid-connect-bundle) for OIDC login. -``` shell -task console -- list app:user +The bundle is configured with some environment variables: + +``` dotenv +# .env.local +ADMIN_OIDC_ALLOW_HTTP=false +# Get these from your IdP provider +ADMIN_OIDC_METADATA_URL=https://…/.well-known/openid-configuration +ADMIN_OIDC_CLIENT_ID=… +ADMIN_OIDC_CLIENT_SECRET=… + +ADMIN_OIDC_REDIRECT_URI=https://rpa-process-overview.example.com/ + +ADMIN_OIDC_ROLE_MAP='{ + "overview-manager": ["ROLE_OVERVIEW_MANAGER"] +}' ``` -to see the list of user related commands. +For local testing of OIDC login, we use [OpenId Connect Server Mock](https://github.com/Soluto/oidc-server-mock) (cf. +[`docker-compose.oidc.yml`](docker-compose.oidc.yml)) and the mock is running on +. + +The mock provides these users (cf. [`docker-compose.oidc.yml`](docker-compose.oidc.yml)): + +| Username | Password | Roles | +|------------------|------------------|------------------| +| admin | admin | administrator | +| overview-manager | overview-manager | overview-manager | +| user | user | user | + +> [!TIP] +> Set `DOCKER_OIDC_DISABLE` to a non-empty value in `.env.local` to disable the OIDC service, e.g. +> +> ``` dotend +> # .env.local +> DOCKER_OIDC_DISABLE=true diff --git a/composer.json b/composer.json index ba4bb4d..3253a13 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "doctrine/doctrine-migrations-bundle": "^3.4.2", "doctrine/orm": "^3.5.2", "easycorp/easyadmin-bundle": "^4.25.1", + "itk-dev/openid-connect-bundle": "^4.0", "league/uri-components": "^7.5.1", "runtime/frankenphp-symfony": "^0.2.0", "stof/doctrine-extensions-bundle": "^1.14", diff --git a/composer.lock b/composer.lock index 6858bdc..7f1d885 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": "c86c9f1ba9f64bd04b3d9ba4896749d0", + "content-hash": "9ab9315fdca2ad1a44b0e4087c7b94f1", "packages": [ { "name": "composer/semver", @@ -1301,6 +1301,69 @@ ], "time": "2025-09-10T05:00:12+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v6.11.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + }, + "time": "2025-04-09T20:32:01+00:00" + }, { "name": "gedmo/doctrine-extensions", "version": "v3.21.0", @@ -1433,6 +1496,457 @@ ], "time": "2025-09-22T17:04:34+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "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.10.0" + }, + "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": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "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.3.0" + }, + "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": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "21dc724a0583619cd1652f673303492272778051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", + "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.44 || ^9.6.25" + }, + "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.8.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": "2025-08-23T21:21:41+00:00" + }, + { + "name": "itk-dev/openid-connect", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/itk-dev/openid-connect.git", + "reference": "8f3b8c0cc4abc7e91c1ee0f3e978deee6806da6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/8f3b8c0cc4abc7e91c1ee0f3e978deee6806da6d", + "reference": "8f3b8c0cc4abc7e91c1ee0f3e978deee6806da6d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "^6.8", + "league/oauth2-client": "^2.6", + "php": "^8.3", + "psr/cache": "^2.0 || ^3.0", + "psr/http-client": "^1.0", + "robrichards/xmlseclibs": "^3.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "escapestudios/symfony2-coding-standard": "^3.12", + "mockery/mockery": "^1.4", + "phpstan/phpstan": "^2.1", + "phpunit/php-code-coverage": "^11.0", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ItkDev\\OpenIdConnect\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeppe Kuhlmann Andersen", + "email": "jekua@aarhus.dk" + }, + { + "name": "Ture Gjørup", + "email": "tug@aarhus.dk" + }, + { + "name": "Lars Steen Risom", + "email": "lats@aarhus.dk" + } + ], + "description": "OpenID connect configuration package", + "support": { + "issues": "https://github.com/itk-dev/openid-connect/issues", + "source": "https://github.com/itk-dev/openid-connect/tree/4.0.1" + }, + "time": "2025-01-13T09:01:13+00:00" + }, + { + "name": "itk-dev/openid-connect-bundle", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/itk-dev/openid-connect-bundle.git", + "reference": "be2dff91f114ba26926b5d59e9db3632cf809e7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/be2dff91f114ba26926b5d59e9db3632cf809e7d", + "reference": "be2dff91f114ba26926b5d59e9db3632cf809e7d", + "shasum": "" + }, + "require": { + "doctrine/orm": "^2.8|^3.0", + "ext-json": "*", + "ext-openssl": "*", + "itk-dev/openid-connect": "^4.0", + "php": "^8.3", + "symfony/cache": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4.13|^7.0", + "symfony/security-bundle": "^6.4.13|^7.0", + "symfony/uid": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.28", + "escapestudios/symfony2-coding-standard": "^3.12", + "friendsofphp/php-cs-fixer": "^3.11", + "kubawerlos/php-cs-fixer-custom-fixers": "^3.11", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0", + "rector/rector": "^2.0", + "symfony/runtime": "^6.4.13|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "ItkDev\\OpenIdConnectBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeppe Kuhlmann Andersen", + "email": "jekua@aarhus.dk" + }, + { + "name": "Ture Gjørup", + "email": "tug@aarhus.dk" + } + ], + "description": "Symfony bundle for openid-connect", + "support": { + "issues": "https://github.com/itk-dev/openid-connect-bundle/issues", + "source": "https://github.com/itk-dev/openid-connect-bundle/tree/4.0.1" + }, + "time": "2025-01-16T21:14:01+00:00" + }, { "name": "lcobucci/jwt", "version": "5.5.0", @@ -1506,6 +2020,71 @@ ], "time": "2025-01-26T21:29:45+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": "league/uri", "version": "7.5.1", @@ -1962,6 +2541,58 @@ }, "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", @@ -2176,6 +2807,92 @@ }, "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": "robrichards/xmlseclibs", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/2bdfd742624d739dfadbd415f00181b4a77aaf07", + "reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">= 5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP library for XML Security", + "homepage": "https://github.com/robrichards/xmlseclibs", + "keywords": [ + "security", + "signature", + "xml", + "xmldsig" + ], + "support": { + "issues": "https://github.com/robrichards/xmlseclibs/issues", + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.3" + }, + "time": "2024-11-20T21:13:56+00:00" + }, { "name": "runtime/frankenphp-symfony", "version": "0.2.0", diff --git a/config/bundles.php b/config/bundles.php index 900b6d0..155e49b 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -19,4 +19,5 @@ Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], + ItkDev\OpenIdConnectBundle\ItkDevOpenIdConnectBundle::class => ['all' => true], ]; diff --git a/config/packages/itkdev_openid_connect.yaml b/config/packages/itkdev_openid_connect.yaml new file mode 100644 index 0000000..f995883 --- /dev/null +++ b/config/packages/itkdev_openid_connect.yaml @@ -0,0 +1,20 @@ +itkdev_openid_connect: + cache_options: + cache_pool: 'cache.app' # Cache item pool for caching discovery document and CLI login tokens + cli_login_options: + route: '%env(string:OIDC_CLI_LOGIN_ROUTE)%' # Redirect route for CLI login + user_provider: ~ # + openid_providers: + admin: + options: + metadata_url: '%env(string:ADMIN_OIDC_METADATA_URL)%' + client_id: '%env(string:ADMIN_OIDC_CLIENT_ID)%' + client_secret: '%env(string:ADMIN_OIDC_CLIENT_SECRET)%' + # Specify redirect URI + redirect_uri: '%env(string:ADMIN_OIDC_REDIRECT_URI)%' + # Optional: Specify leeway (seconds) to account for clock skew between provider and hosting + # Defaults to 10 + leeway: '%env(int:ADMIN_OIDC_LEEWAY)%' + # Optional: Allow (non-secure) http requests (used for mocking a IdP). NOT RECOMMENDED FOR PRODUCTION. + # Defaults to false + allow_http: '%env(bool:ADMIN_OIDC_ALLOW_HTTP)%' diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml index 8166181..0f34f87 100644 --- a/config/packages/routing.yaml +++ b/config/packages/routing.yaml @@ -2,7 +2,7 @@ framework: router: # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands - #default_uri: http://localhost + default_uri: '%env(DEFAULT_URI)%' when@prod: framework: diff --git a/config/packages/security.yaml b/config/packages/security.yaml index ed0dc77..a72f273 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,29 +1,21 @@ security: - # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords - password_hashers: - Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - # used to reload user from session & other features (e.g. switch_user) app_user_provider: entity: class: App\Entity\User property: email + firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true - provider: app_user_provider - form_login: - login_path: app_login - check_path: app_login - enable_csrf: true - logout: - path: app_logout - # where to redirect after logout - # target: app_any_route + custom_authenticators: + - App\Security\OidcAuthenticator + - ItkDev\OpenIdConnectBundle\Security\CliLoginTokenAuthenticator + entry_point: App\Security\OidcAuthenticator # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall @@ -31,22 +23,16 @@ security: # https://symfony.com/doc/current/security/impersonating_user.html # switch_user: true + role_hierarchy: + !php/enum App\Entity\UserRole::Admin->value: + - !php/enum App\Entity\UserRole::User->value + !php/enum App\Entity\UserRole::OverviewManager->value: + - !php/enum App\Entity\UserRole::Admin->value + # 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: ^/admin, roles: ROLE_ADMIN } + - { path: ^/admin, roles: !php/enum App\Entity\UserRole::Admin->value } - { path: ^/login, roles: PUBLIC_ACCESS } - - { path: ^/, roles: ROLE_USER } - -when@test: - security: - password_hashers: - # By default, password hashers are resource intensive and take time. This is - # important to generate secure password hashes. In tests however, secure hashes - # are not important, waste resources and increase test times. The following - # reduces the work factor to the lowest possible values. - Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: - algorithm: auto - cost: 4 # Lowest possible value for bcrypt - time_cost: 3 # Lowest possible value for argon - memory_cost: 10 # Lowest possible value for argon + - { path: '^/openidconnect/login(/.+)?$', role: PUBLIC_ACCESS } + - { path: ^/, roles: !php/enum App\Entity\UserRole::User->value } diff --git a/config/routes/itkdev_openid_connect.yaml b/config/routes/itkdev_openid_connect.yaml new file mode 100644 index 0000000..1c60325 --- /dev/null +++ b/config/routes/itkdev_openid_connect.yaml @@ -0,0 +1,3 @@ +itkdev_openid_connect: + resource: '@ItkDevOpenIdConnectBundle/src/Resources/config/routes.yaml' + prefix: '/openidconnect' # Prefix for bundle routes diff --git a/config/services.yaml b/config/services.yaml index 65b6a95..354770e 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -20,7 +20,7 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones - App\Controller\SecurityController: + App\Security\OidcAuthenticator: arguments: $options: - page_title: '%site_title%' + role_map: '%env(json:ADMIN_OIDC_ROLE_MAP)%' diff --git a/docker-compose.oidc.yml b/docker-compose.oidc.yml new file mode 100644 index 0000000..ef39741 --- /dev/null +++ b/docker-compose.oidc.yml @@ -0,0 +1,123 @@ +services: + # https://github.com/Soluto/oidc-server-mock + oidc-idp: + image: ghcr.io/soluto/oidc-server-mock:latest + profiles: + - ${DOCKER_OIDC_DISABLE:-} + # Let this container be accessible both internally and externally on the same domain. + container_name: idp.${COMPOSE_DOMAIN} + networks: + - app + - frontend + ports: + - "80" + volumes: + - .:/tmp/config:ro + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-idp.rule=Host(`idp.${COMPOSE_DOMAIN}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME}-idp.loadbalancer.server.port=443" + - "traefik.http.services.${COMPOSE_PROJECT_NAME}-idp.loadbalancer.server.scheme=https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-idp.middlewares=redirect-to-https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + environment: + # https://github.com/Soluto/oidc-server-mock?tab=readme-ov-file#https + ASPNETCORE_URLS: https://+:443;http://+:80 + ASPNETCORE_Kestrel__Certificates__Default__Password: mock + ASPNETCORE_Kestrel__Certificates__Default__Path: /tmp/config/.docker/oidc-server-mock/cert/docker.pfx + + ASPNETCORE_ENVIRONMENT: Development + SERVER_OPTIONS_INLINE: | + AccessTokenJwtType: JWT + Discovery: + ShowKeySet: true + Authentication: + CookieSameSiteMode: Lax + CheckSessionCookieSameSiteMode: Lax + + LOGIN_OPTIONS_INLINE: | + { + "AllowRememberLogin": false + } + + LOGOUT_OPTIONS_INLINE: | + { + "AutomaticRedirectAfterSignOut": true + } + + CLIENTS_CONFIGURATION_INLINE: | + - ClientId: client-id + ClientSecrets: [client-secret] + Description: Mock IdP + AllowedGrantTypes: + # - client_credentials + # - implicit + - authorization_code + # https://github.com/Soluto/oidc-server-mock/issues/46#issuecomment-704963181 + RequireClientSecret: false + AllowAccessTokensViaBrowser: true + # https://github.com/Soluto/oidc-server-mock/issues/26#issuecomment-705022941 + AlwaysIncludeUserClaimsInIdToken: true + AllowedScopes: + - openid + - profile + - email + ClientClaimsPrefix: '' + RedirectUris: + - '*' + # https://github.com/Soluto/oidc-server-mock/issues/60 + PostLogoutRedirectUris: + - '*' + # https://github.com/Soluto/oidc-server-mock/issues/46#issuecomment-704845375 + RequirePkce: false + + # Needed to set custom claim types in "profile" + # https://github.com/Soluto/oidc-server-mock/issues/123#issuecomment-1427129278 + # https://github.com/Soluto/oidc-server-mock/blob/master/README.md#simple-configuration + # https://docs.docker.com/compose/compose-file/compose-file-v3/#environment + OVERRIDE_STANDARD_IDENTITY_RESOURCES: "true" + IDENTITY_RESOURCES_INLINE: | + # https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims + - Name: openid + ClaimTypes: + - sub + - Name: email + ClaimTypes: + - email + - Name: profile + ClaimTypes: + # Add your custom claims here + - name + - groups + - upn + + USERS_CONFIGURATION_INLINE: | + - SubjectId: 1 + Username: overview-manager + Password: overview-manager + Claims: + # Claims added here must be defined above in IDENTITY_RESOURCES_INLINE + - Type: email + Value: 'overview-manager@example.com' + ValueType: string + - Type: groups + Value: '["overview-manager"]' + ValueType: json + - Type: upn + Value: 'overview-manager@example.com' + ValueType: string + + - SubjectId: 2 + Username: user + Password: user + Claims: + - Type: email + Value: 'user@example.com' + ValueType: string + - Type: groups + Value: '["user"]' + ValueType: json + - Type: upn + Value: 'user@example.com' + ValueType: string diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 80f9f65..55481c5 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,5 +1,6 @@ include: - docker-compose.api.yml + - docker-compose.oidc.yml services: phpfpm: diff --git a/fixtures/user.yaml b/fixtures/user.yaml index 430772c..0f85688 100644 --- a/fixtures/user.yaml +++ b/fixtures/user.yaml @@ -1,12 +1,10 @@ App\Entity\User: - admin: - email: admin@example.com - password: '' + overview-manager: + email: overview-manager@example.com roles: - - ROLE_ADMIN + - ROLE_OVERVIEW_MANAGER user: email: user@example.com - password: '' roles: - ROLE_USER diff --git a/migrations/Version20251003082431.php b/migrations/Version20251003082431.php new file mode 100644 index 0000000..40aee35 --- /dev/null +++ b/migrations/Version20251003082431.php @@ -0,0 +1,36 @@ +addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, created_by, updated_by, created_at, updated_at FROM user'); + $this->addSql('DROP TABLE user'); + $this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL)'); + $this->addSql('INSERT INTO user (id, email, roles, created_by, updated_by, created_at, updated_at) SELECT id, email, roles, created_by, updated_by, created_at, updated_at FROM __temp__user'); + $this->addSql('DROP TABLE __temp__user'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user ADD COLUMN password VARCHAR(255) NOT NULL'); + } +} diff --git a/src/Command/UserCreateCommand.php b/src/Command/UserCreateCommand.php deleted file mode 100644 index 0f13ad0..0000000 --- a/src/Command/UserCreateCommand.php +++ /dev/null @@ -1,65 +0,0 @@ -addArgument('email', InputArgument::OPTIONAL, 'User email') - ->addOption('password', null, InputOption::VALUE_REQUIRED, 'User password'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $email = $input->getArgument('email'); - while (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - $email = $io->ask('Email'); - } - - $user = $this->userRepository->findOneBy(['email' => $email]); - if (null !== $user) { - throw new InvalidArgumentException(sprintf('User %s already exists.', $user->getUserIdentifier())); - } - - $password = $input->getOption('password'); - while (empty(trim($password))) { - $password = $io->ask('Password'); - } - - $user = new User(); - $user - ->setEmail($email) - ->setPassword($this->passwordHasher->hashPassword($user, $password)); - $this->userRepository->save($user, flush: true); - - $io->success(sprintf('User %s created.', $user->getUserIdentifier())); - - return Command::SUCCESS; - } -} diff --git a/src/Command/UserRolesCommand.php b/src/Command/UserRolesCommand.php deleted file mode 100644 index 4f2e7d3..0000000 --- a/src/Command/UserRolesCommand.php +++ /dev/null @@ -1,64 +0,0 @@ -addArgument('email', InputArgument::REQUIRED, 'User email') - ->addOption('add', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Roles to add') - ->addOption('remove', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Roles to removea'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $email = $input->getArgument('email'); - - $user = $this->userRepository->findOneBy(['email' => $email]); - if (null === $user) { - throw new InvalidArgumentException(sprintf('Cannot find user %s.', $email)); - } - - $roles = $user->getRoles(); - $roles = array_merge($roles, $input->getOption('add')); - $roles = array_diff($roles, $input->getOption('remove')); - - $invalidRoles = array_filter($roles, static fn (string $role) => null === UserRole::tryFrom($role)); - if (!empty($invalidRoles)) { - throw new InvalidArgumentException(sprintf('Invalid roles: %s', implode(', ', $invalidRoles))); - } - - $user->setRoles($roles); - $this->userRepository->save($user, flush: true); - - $io->success(sprintf('Roles for ser %s: %s.', $user->getUserIdentifier(), implode(', ', $user->getRoles()))); - - return Command::SUCCESS; - } -} diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 4db9237..518d7b3 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -8,9 +8,11 @@ use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; +use EasyCorp\Bundle\EasyAdminBundle\Config\UserMenu; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\User\UserInterface; use function Symfony\Component\Translation\t; @@ -30,6 +32,14 @@ public function configureDashboard(): Dashboard ->setTitle($this->getParameter('site_title')); } + public function configureUserMenu(UserInterface $user): UserMenu + { + return parent::configureUserMenu($user) + ->setMenuItems([ + // Remove the logout link. + ]); + } + public function configureMenuItems(): iterable { yield MenuItem::linkToCrud(t('Group'), null, ProcessOverviewGroup::class); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 6ac5354..38a1c71 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -5,65 +5,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; -use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; class SecurityController extends AbstractController { - public function __construct( - private readonly array $options, - ) { - } - #[Route(path: '/login', name: 'app_login')] - public function login(AuthenticationUtils $authenticationUtils): Response - { - // https://symfony.com/bundles/EasyAdminBundle/current/dashboards.html#login-form-template - $error = $authenticationUtils->getLastAuthenticationError(); - $lastUsername = $authenticationUtils->getLastUsername(); - - return $this->render('@EasyAdmin/page/login.html.twig', [ - // parameters usually defined in Symfony login forms - 'error' => $error, - 'last_username' => $lastUsername, - - // the translation_domain to use (define this option only if you are - // rendering the login template in a regular Symfony controller; when - // rendering it from an EasyAdmin Dashboard this is automatically set to - // the same domain as the rest of the Dashboard) - 'translation_domain' => 'admin', - - // by default EasyAdmin displays a black square as its default favicon; - // use this method to display a custom favicon: the given path is passed - // "as is" to the Twig asset() function: - // - 'favicon_path' => '/favicon.svg', - - // the title visible above the login form (define this option only if you are - // rendering the login template in a regular Symfony controller; when rendering - // it from an EasyAdmin Dashboard this is automatically set as the Dashboard title) - 'page_title' => $this->options['page_title'] ?? 'RPA Process Overview', - - // the string used to generate the CSRF token. If you don't define - // this parameter, the login form won't include a CSRF token - 'csrf_token_intention' => 'authenticate', - - // the URL users are redirected to after the login (default: '/admin') - 'target_path' => $this->generateUrl('app_default'), - - // the label displayed for the username form field (the |trans filter is applied to it) - 'username_label' => 'Email', - - // the label displayed for the password form field (the |trans filter is applied to it) - 'password_label' => 'Password', - - // the label displayed for the Sign In form button (the |trans filter is applied to it) - 'sign_in_label' => 'Log in', - ]); - } - - #[Route(path: '/logout', name: 'app_logout')] - public function logout(): void + public function login(): Response { - throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); + return $this->redirectToRoute('itkdev_openid_connect_login', ['providerKey' => 'admin']); } } diff --git a/src/DataFixtures/Faker/Provider/Provider.php b/src/DataFixtures/Faker/Provider/Provider.php deleted file mode 100644 index ef2cce9..0000000 --- a/src/DataFixtures/Faker/Provider/Provider.php +++ /dev/null @@ -1,35 +0,0 @@ -' - * - * @return string - */ - public function password(PasswordAuthenticatedUserInterface $user, string $plaintextPassword) - { - return $this->passwordHasher->hashPassword( - $user, - $plaintextPassword - ); - } -} diff --git a/src/Entity/User.php b/src/Entity/User.php index ce0e8b3..991eef8 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -6,12 +6,11 @@ use Doctrine\ORM\Mapping as ORM; use Gedmo\Blameable\Traits\BlameableEntity; use Gedmo\Timestampable\Traits\TimestampableEntity; -use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])] -class User implements UserInterface, PasswordAuthenticatedUserInterface +class User implements UserInterface { use BlameableEntity; use TimestampableEntity; @@ -30,12 +29,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private array $roles = []; - /** - * @var string The hashed password - */ - #[ORM\Column] - private ?string $password = null; - public function getId(): ?int { return $this->id; @@ -85,30 +78,9 @@ public function setRoles(array $roles): static return $this; } - /** - * @see PasswordAuthenticatedUserInterface - */ - public function getPassword(): ?string - { - return $this->password; - } - - public function setPassword(string $password): static - { - $this->password = $password; - - return $this; - } - - /** - * Ensure the session doesn't contain actual password hashes by CRC32C-hashing them, as supported since Symfony 7.3. - */ public function __serialize(): array { - $data = (array) $this; - $data["\0".self::class."\0password"] = hash('crc32c', $this->password); - - return $data; + return (array) $this; } #[\Deprecated] diff --git a/src/Entity/UserRole.php b/src/Entity/UserRole.php index 6560f03..fa9a1f7 100644 --- a/src/Entity/UserRole.php +++ b/src/Entity/UserRole.php @@ -4,6 +4,7 @@ enum UserRole: string { - case ROLE_USER = 'ROLE_USER'; - case ROLE_ADMIN = 'ROLE_ADMIN'; + case User = 'ROLE_USER'; + case Admin = 'ROLE_ADMIN'; + case OverviewManager = 'ROLE_OVERVIEW_MANAGER'; } diff --git a/src/Security/OidcAuthenticator.php b/src/Security/OidcAuthenticator.php new file mode 100644 index 0000000..1f3dd6f --- /dev/null +++ b/src/Security/OidcAuthenticator.php @@ -0,0 +1,75 @@ +validateClaims($request); + + // Extract properties from claims + $email = $claims['email'] ?? $claims['upn'] ?? null; + $roles = $claims['roles'] ?? $claims['groups'] ?? []; + + // Check if user already exists already or create a new one. + $user = $this->userRepository->findOneBy(['email' => $email]) ?? new User(); + + if (is_array($roles)) { + $map = (array) ($this->options['role_map'] ?? null); + $userRoles = array_map(static fn (string $role) => (array) ($map[$role] ?? null), $roles); + // Flatten and filter out invalid roles. + $userRoles = array_filter(array_merge(...$userRoles), static fn (string $role) => null !== UserRole::tryFrom($role)); + $user->setRoles($userRoles); + } + + $user->setEmail($email); + $this->userRepository->save($user, flush: true); + + return new SelfValidatingPassport(new UserBadge($user->getUserIdentifier())); + } catch (ItkOpenIdConnectException|InvalidProviderException $exception) { + throw new CustomUserMessageAuthenticationException($exception->getMessage()); + } + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return new RedirectResponse($this->router->generate('app_default')); + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + return new RedirectResponse($this->router->generate('itkdev_openid_connect_login', [ + 'providerKey' => 'admin', + ])); + } +} diff --git a/symfony.lock b/symfony.lock index 46d475c..eb6fa4d 100644 --- a/symfony.lock +++ b/symfony.lock @@ -72,6 +72,9 @@ "fixtures/.gitignore" ] }, + "itk-dev/openid-connect-bundle": { + "version": "4.0.1" + }, "nelmio/alice": { "version": "3.14", "recipe": { diff --git a/templates/default/index.html.twig b/templates/default/index.html.twig index 6e13814..5028f3a 100644 --- a/templates/default/index.html.twig +++ b/templates/default/index.html.twig @@ -1,8 +1,9 @@ {% extends 'base.html.twig' %} {% block content %} + {{ dump(enum('App\\Entity\\UserRole').Admin) }}