From 3b0ce1314f565f54287328954d744e8861d5c752 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Thu, 15 Jan 2026 18:04:01 +0100 Subject: [PATCH] =?UTF-8?q?```=20=E2=9C=A8=20feat(security):=20Ajoute=20l'?= =?UTF-8?q?authentification=20Keycloak=20SSO=20et=20migre=20les=20commande?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supprime la commande AccountCommand, la migration et ajoute l'authentification Keycloak SSO. Crée les vues de base pour le tableau de bord. ``` --- .env | 4 + .gitea/workflows/install-deps.yml | 34 + ansible/hosts.ini | 2 + ansible/playbook.yml | 220 ++++ ansible/templates/caddy.j2 | 21 + ansible/templates/supervisor.j2 | 17 + composer.json | 50 +- composer.lock | 967 ++++++++++-------- config/bundles.php | 1 + config/packages/knpu_oauth2_client.yaml | 13 + config/packages/security.yaml | 4 +- docker-compose.yml | 57 +- makefile | 3 +- ...09163956.php => Version20251211203538.php} | 2 +- migrations/Version20260115165200.php | 44 + migrations/Version20260115165808.php | 32 + public/assets/images/logo.png | Bin 0 -> 51402 bytes src/Command/AccountCommand.php | 66 -- src/Controller/Dashboard/HomeController.php | 40 + src/Controller/HomeController.php | 21 +- src/Entity/Account.php | 49 +- src/Security/KeycloakAuthenticator.php | 101 ++ src/Service/Mailer/MailerSubscriber.php | 2 +- symfony.lock | 12 + templates/base.twig | 2 +- templates/dashboard/administrateur.twig | 5 + templates/dashboard/base.twig | 183 ++++ templates/dashboard/home.twig | 2 + templates/home.twig | 7 + templates/mails/base.twig | 78 +- templates/mails/new_admin.twig | 2 +- .../security/forgot-password-confirm.twig | 1 + templates/security/forgot_password.twig | 1 + .../security/forgot_password_success.twig | 1 + templates/txt-mails/base.twig | 13 + templates/txt-mails/new_admin.twig | 25 + translations/messages.fr.yaml | 1 + update.sh | 6 +- 38 files changed, 1485 insertions(+), 604 deletions(-) create mode 100644 .gitea/workflows/install-deps.yml create mode 100644 ansible/hosts.ini create mode 100644 ansible/playbook.yml create mode 100644 ansible/templates/caddy.j2 create mode 100644 ansible/templates/supervisor.j2 create mode 100644 config/packages/knpu_oauth2_client.yaml rename migrations/{Version20251209163956.php => Version20251211203538.php} (98%) create mode 100644 migrations/Version20260115165200.php create mode 100644 migrations/Version20260115165808.php create mode 100644 public/assets/images/logo.png delete mode 100644 src/Command/AccountCommand.php create mode 100644 src/Controller/Dashboard/HomeController.php create mode 100644 src/Security/KeycloakAuthenticator.php create mode 100644 templates/dashboard/administrateur.twig create mode 100644 templates/dashboard/base.twig create mode 100644 templates/dashboard/home.twig create mode 100644 templates/txt-mails/base.twig create mode 100644 templates/txt-mails/new_admin.twig diff --git a/.env b/.env index c07a872..24cea0d 100644 --- a/.env +++ b/.env @@ -67,3 +67,7 @@ GOOGLE_APPLICATION_CREDENTIALS=%kernel.project_dir%/google.json SENTRY_DSN="" ###< sentry/sentry-symfony ### DEFAULT_URI=https://esyweb.local +KEYCLOAK_AUTH_SERVER_URL=https://auth.esy-web.dev +KEYCLOAK_REALM=master +KEYCLOAK_CLIENT_ID=ludikevent +KEYCLOAK_CLIENT_SECRET=FA7ue4h6rKL0bFZSEXxoZ4uh5LIohsyd diff --git a/.gitea/workflows/install-deps.yml b/.gitea/workflows/install-deps.yml new file mode 100644 index 0000000..2f91076 --- /dev/null +++ b/.gitea/workflows/install-deps.yml @@ -0,0 +1,34 @@ +# Nom du workflow +name: Symfony CI - Install, Test, Build, Attest & Deploy + +# Déclencheurs du workflow +on: + push: + branches: + - master # Ou 'main' + pull_request: + types: [opened, synchronize, reopened] + branches: + - master # Ou 'main' + +# Permissions nécessaires pour les actions utilisées +permissions: + contents: read + pull-requests: write + id-token: write + attestations: write + security-events: write # Requis pour Snyk pour poster les résultats + +jobs: + deploy: + name: 🚀 Deploy to Production + steps: + - name: Deploy with SSH & Ansible + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + script: | + cd /var/www/ludikevent-intranet && git pull && nohup sh ./update.sh diff --git a/ansible/hosts.ini b/ansible/hosts.ini new file mode 100644 index 0000000..73b3127 --- /dev/null +++ b/ansible/hosts.ini @@ -0,0 +1,2 @@ +[webservers] +127.0.0.1 ansible_connection=local ansible_python_interpreter=/usr/bin/python3 path=/var/www/ludikevent-intranet diff --git a/ansible/playbook.yml b/ansible/playbook.yml new file mode 100644 index 0000000..96eabc9 --- /dev/null +++ b/ansible/playbook.yml @@ -0,0 +1,220 @@ +# Fichier: install_php_83_symfony_pgsql.yml + +- name: Deploy application + hosts: webservers + become: true + gather_facts: true + + vars: + db_name: "ludikevent" + db_user: "ludikevent" + db_password: "ludikevent" + redis_password: "ludikevent" + redis_port: "20110" + # Assurez-vous que 'path' est définie dans votre inventaire ou comme extra-var + # Exemple: path: /var/www/mainframe/app + + tasks: + - name: Exécuter 'composer install' dans le répertoire de l'application + ansible.builtin.command: composer install --no-dev --optimize-autoloader + become: false # Run as the connection user (e.g., 'bot') + args: + chdir: "{{ path }}" + when: ansible_os_family == "Debian" + - name: Send a message to the Discord channel + community.general.discord: + webhook_id: "1419573620602044518" + webhook_token: "ikAdxWxsrrTqMTb5Gh_8ylcoJHlOnq7aJZvR5udoS_fCK56Jk3qpEnJHVKdD8fwuNJF3" + content: "Mise à jour du intranet ludikevent" + + - name: Installer le support ACL pour corriger les permissions de 'become_user' + ansible.builtin.apt: + name: acl + state: present + update_cache: true + when: ansible_os_family == "Debian" + + - name: Installation des dépendances pour le module Ansible PostgreSQL + ansible.builtin.apt: + name: python3-psycopg2 + state: present + update_cache: true + when: ansible_os_family == "Debian" + + - name: Installation de PHP 8.3 et PHP 8.3-FPM avec les dépendances + ansible.builtin.apt: + name: + - php8.3 + - php8.3-fpm + - php8.3-cli + - php8.3-common + - php8.3-mysql + - php8.3-pgsql + - php8.3-xml + - php8.3-mbstring + - php8.3-zip + - php8.3-intl + - php8.3-gd + - php8.3-curl + - php8.3-pdo + - php8.3-opcache + - php8.3-bcmath + - php8.3-redis + - php8.3-imagick + - ffmpeg + state: present + when: ansible_os_family == "Debian" + + - name: Démarrage et activation du service PHP 8.3 FPM + ansible.builtin.systemd: + name: php8.3-fpm + state: started + enabled: yes + when: ansible_os_family == "Debian" + + - name: Créer le fichier .env.local avec les secrets de production + ansible.builtin.copy: + content: | + APP_ENV=prod + VITE_LOAD=1 + DATABASE_URL="postgresql://{{ db_user }}:{{ db_password }}@127.0.0.1:5432/{{ db_name }}?serverVersion=16&charset=utf8" + REDIS_DSN="redis://{{ redis_password }}@127.0.0.1:{{ redis_port }}" + REDIS_URL="redis://{{ redis_password }}@127.0.0.1:{{ redis_port }}" + MESSENGER_TRANSPORT_DSN="redis://{{ redis_password }}@127.0.0.1:{{ redis_port }}/messages" + APP_SECRET=939bbc67038c2e2d1232d86fc605bf2f + REAL_MAIL=1 + VAULT_ADDR=http://127.0.0.1:8200 + VAULT_TOKEN=hvs.QLpUdiptXtSPo5Qf7i2nn2Xz + MAILER_DSN=ses+smtp://AKIAWTT2T22CWBRBBDYN:BBdgb6KxRQ8mNcpWFJsZCJxbSGNdgLhKFiITMErfBlQP@default?region=eu-west-3 + + dest: "{{ path }}/.env.local" + when: ansible_os_family == "Debian" + + # --- Initial creation of essential directories with correct ownership --- + # These directories should exist before composer runs, but composer might create subdirs. + - name: Ensure app/var and public/media directories exist with correct owner/group + ansible.builtin.file: + path: "{{ item }}" + owner: bot # Assuming 'bot' is your deployment user + group: www-data + mode: '0775' # Allow 'bot' and 'www-data' to read/write/execute + state: directory + recurse: yes # Important to ensure subdirectories created by previous deploys also get permissions + loop: + - "{{ path }}/var" + - "{{ path }}/var/log" # Specific for log, though var/log might be created by composer later + - "{{ path }}/public/media" # For uploads + - "{{ path }}/public/storage" # For uploads + - "{{ path }}/public/tmp-sign" # For uploads + + # --- POST-COMPOSER PERMISSION FIXES --- + # This is crucial because composer creates var/cache as the `become: false` user + - name: Set correct permissions for Symfony cache and logs directories + ansible.builtin.file: + path: "{{ item }}" + owner: bot + group: www-data + mode: '0775' # rwx for owner and group, rx for others + state: directory + recurse: yes # Apply to all contents + loop: + - "{{ path }}/var/cache" + - "{{ path }}/var/log" + # For web-writable directories created by the app itself (e.g., uploads), you might set ACLs + # or chown to www-data and then your user gets access via group membership. + + # Alternative for cache/log permissions using ACLs (more robust for mixed ownership) + # This requires 'acl' package installed (which you already do). + # Use this if 'bot' needs to own, but www-data needs to write. + - name: Set ACLs for Symfony cache and logs (recommended for web-writable dirs) + ansible.builtin.acl: + path: "{{ item }}" + entity: www-data + etype: group + permissions: rwx + state: present + recursive: yes + default: yes # Apply default ACLs for new files/dirs within + loop: + - "{{ path }}/var/cache" + - "{{ path }}/var/log" + when: ansible_os_family == "Debian" # ACLs are Linux-specific + + - name: Exécuter bun install dans le répertoire de l application + ansible.builtin.command: bun install + become: false + args: + chdir: "{{ path }}" + when: ansible_os_family == "Debian" + + - name: Exécuter bun build dans le répertoire de l application + ansible.builtin.command: bun run build + become: false + args: + chdir: "{{ path }}" + when: ansible_os_family == "Debian" + + - name: Supervisor config + ansible.builtin.template: + src: supervisor.j2 + dest: "/etc/supervisor/conf.d/mainframe.conf" + mode: '0644' + + - name: Reread Supervisor configuration + ansible.builtin.command: supervisorctl reread + changed_when: true # Always mark as changed, as output is not always useful for idempotency + + - name: Update Supervisor (add/remove updated programs) + ansible.builtin.command: supervisorctl update + changed_when: true + + - name: Purger la base de données Redis + ansible.builtin.command: "redis-cli -p {{ redis_port }} -a {{ redis_password }} FLUSHALL" + when: ansible_os_family == "Debian" + + - name: Generate Caddy site configuration + ansible.builtin.template: + src: caddy.j2 + dest: "/etc/caddy/sites/mainframe.conf" + mode: '0644' + + - name: Reload Caddy to apply new configuration + ansible.builtin.systemd: + name: caddy + state: reloaded + enabled: yes + - name: Exécuter doctrine:migration:migrate dans le répertoire de l application + ansible.builtin.command: php bin/console doctrine:migrations:migrate --no-interaction + become: false + args: + chdir: "{{ path }}" + when: ansible_os_family == "Debian" + - name: Exécuter cache:clear dans le répertoire de l application + ansible.builtin.command: php bin/console cache:clear + become: false + args: + chdir: "{{ path }}" + when: ansible_os_family == "Debian" + + - name: Exécuter liip:imagine:cache:remove dans le répertoire de l application + ansible.builtin.command: php bin/console liip:imagine:cache:remove + become: false + args: + chdir: "{{ path }}" + when: ansible_os_family == "Debian" # Added a when condition here, often missed + + - name: Set correct permissions for Symfony cache and logs directories + ansible.builtin.file: + path: "{{ item }}" + owner: bot + group: www-data + mode: '0777' # rwx for owner and group, rx for others + state: directory + recurse: yes # Apply to all contents + loop: + - "{{ path }}/var/cache" + - "{{ path }}/var/log" + - "{{ path }}/public/media" + - "{{ path }}/public/storage" # For uploads + - "{{ path }}/public/tmp-sign" # For uploads + diff --git a/ansible/templates/caddy.j2 b/ansible/templates/caddy.j2 new file mode 100644 index 0000000..aa1d776 --- /dev/null +++ b/ansible/templates/caddy.j2 @@ -0,0 +1,21 @@ +intranet.ludikevent.fr{ + tls { + dns cloudflare KL6pZ-Z_12_zbnM2TtFDIsKM8A-HLPhU5GJJbKTW + } + root * {{ path }}/public + + file_server + request_body { + max_size 100MB + } + header { + Permissions-Policy "accelerometer=(), autoplay=(), camera=(), clipboard-write=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), usb=(), vr=(), screen-wake-lock=(), xr-spatial-tracking=(), bluetooth=(), ambient-light-sensor=(), battery=(), gamepad=(), notifications=(), push=()" + } + + php_fastcgi unix//run/php/php8.3-fpm.sock { + read_timeout 300s + write_timeout 300s + dial_timeout 100s + env HTTP_PROXY "" + } +} diff --git a/ansible/templates/supervisor.j2 b/ansible/templates/supervisor.j2 new file mode 100644 index 0000000..1c44fcb --- /dev/null +++ b/ansible/templates/supervisor.j2 @@ -0,0 +1,17 @@ +[program:redis_ludikevent_intranet] +command=redis-server --port {{ redis_port }} --requirepass {{ redis_password }} +autostart=true +autorestart=true +user=root +stdout_logfile=/var/www/ludikevent-intranet/var/log/redis_stdout.log +stderr_logfile=/var/www/ludikevent-intranet/var/log/redis_stderr.log + +[program:messenger_redis_ludikevent_intranet] +command=php {{path}}/bin/console messenger:consume async --time-limit=3600 +autostart=true +autorestart=true +user=root +startsecs=0 +startretries=10 +stdout_logfile=/var/www/ludikevent-intranet/var/log/messenger_stderr.log +stderr_logfile=/var/www/ludikevent-intranet/var/log/messenger_stdout.log diff --git a/composer.json b/composer.json index 5c8f742..d59e937 100644 --- a/composer.json +++ b/composer.json @@ -11,40 +11,42 @@ "ext-libxml": "*", "ext-zip": "*", "chillerlan/php-qrcode": ">=5.0.5", - "cocur/slugify": ">=4.6", - "doctrine/dbal": "^3.10.3", - "doctrine/doctrine-bundle": "^2.18.1", + "cocur/slugify": ">=4.7.1", + "doctrine/dbal": "^3.10.4", + "doctrine/doctrine-bundle": "^2.18.2", "doctrine/doctrine-migrations-bundle": "^3.7.0", - "doctrine/orm": "^3.5.7", + "doctrine/orm": "^3.6.1", "docusealco/docuseal-php": "^1.0.5", "endroid/qr-code": ">=6.0.9", "exbil/mailcow-php-api": ">=0.15.0", - "fpdf/fpdf": ">=1.86", - "google/apiclient": "^2.18.4", + "fpdf/fpdf": ">=1.86.1", + "google/apiclient": "^2.19.0", "google/cloud": "^0.296.0", "healey/robots": "^1.0.1", - "imagine/imagine": "^1.5", + "imagine/imagine": "^1.5.2", "io-developer/php-whois": ">=4.1.10", - "knplabs/knp-paginator-bundle": "^6.9.1", + "knplabs/knp-paginator-bundle": "^6.10.0", + "knpuniversity/oauth2-client-bundle": "^2.20", "lasserafn/php-initial-avatar-generator": "^4.5", "league/flysystem-aws-s3-v3": "^3.30.1", - "league/flysystem-bundle": "^3.6", + "league/flysystem-bundle": "^3.6.1", "liip/imagine-bundle": "^2.15", "lufiipe/insee-sierene": ">=1", - "minishlink/web-push": "^9.0.3", + "minishlink/web-push": "^9.0.4", "mittwald/vault-php": "^3.0.2", - "mobiledetect/mobiledetectlib": "^4.8.09", - "nelmio/cors-bundle": "^2.6", + "mobiledetect/mobiledetectlib": "^4.8.10", + "nelmio/cors-bundle": "^2.6.1", "ovh/ovh": ">=3.5", "pear/net_dns2": ">=2.0.7", - "phpdocumentor/reflection-docblock": "^5.6.4", - "phpoffice/phpspreadsheet": ">=5.3", - "phpstan/phpdoc-parser": "^2.3", + "phpdocumentor/reflection-docblock": "^5.6.6", + "phpoffice/phpspreadsheet": ">=5.4", + "phpstan/phpdoc-parser": "^2.3.1", "presta/sitemap-bundle": "^4.2", - "sentry/sentry-symfony": "^5.6", + "sentry/sentry-symfony": "^5.8.3", "setasign/fpdi": "^2.6.4", "spatie/mjml-php": "^1.2.5", "stancer/stancer": ">=2.0.1", + "stevenmaguire/oauth2-keycloak": "^5.1", "symfony/amazon-mailer": "7.3.*", "symfony/asset": "7.3.*", "symfony/asset-mapper": "7.3.*", @@ -59,7 +61,7 @@ "symfony/intl": "7.3.*", "symfony/mailer": "7.3.*", "symfony/mime": "7.3.*", - "symfony/monolog-bundle": "^3.10", + "symfony/monolog-bundle": "^3.11.1", "symfony/notifier": "7.3.*", "symfony/process": "7.3.*", "symfony/property-access": "7.3.*", @@ -76,11 +78,11 @@ "symfony/web-link": "7.3.*", "symfony/yaml": "7.3.*", "tecnickcom/tcpdf": "^6.10.1", - "twig/extra-bundle": "^3.22.1", + "twig/extra-bundle": "^3.22.2", "twig/intl-extra": "^3.22.1", - "twig/twig": "^3.22", - "vich/uploader-bundle": "^2.8.1", - "web-auth/webauthn-lib": ">=5.2.2" + "twig/twig": "^3.22.2", + "vich/uploader-bundle": "^2.9.1", + "web-auth/webauthn-lib": ">=5.2.3" }, "config": { "allow-plugins": { @@ -135,12 +137,12 @@ }, "require-dev": { "fakerphp/faker": "^1.24.1", - "phpunit/phpunit": "^12.4.4", - "rector/rector": "^2.2.8", + "phpunit/phpunit": "^12.5.5", + "rector/rector": "^2.3.1", "symfony/browser-kit": "7.3.*", "symfony/css-selector": "7.3.*", "symfony/debug-bundle": "7.3.*", - "symfony/maker-bundle": "^1.65", + "symfony/maker-bundle": "^1.65.1", "symfony/stopwatch": "7.3.*", "symfony/web-profiler-bundle": "7.3.*" } diff --git a/composer.lock b/composer.lock index 2b72345..f609957 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": "4ab8febdfe6b7b880d54cd11845803da", + "content-hash": "bd9ebbc9c455efb1ac19197eaf03b368", "packages": [ { "name": "async-aws/core", @@ -187,16 +187,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.366.3", + "version": "3.369.13", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "11d27829df69c67506d15e6b057ed928b88c4f05" + "reference": "bedc36250c92b8287be855a2d25427fb0e065483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/11d27829df69c67506d15e6b057ed928b88c4f05", - "reference": "11d27829df69c67506d15e6b057ed928b88c4f05", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bedc36250c92b8287be855a2d25427fb0e065483", + "reference": "bedc36250c92b8287be855a2d25427fb0e065483", "shasum": "" }, "require": { @@ -210,7 +210,7 @@ "mtdowling/jmespath.php": "^2.8.0", "php": ">=8.1", "psr/http-message": "^1.0 || ^2.0", - "symfony/filesystem": "^v6.4.3 || ^v7.1.0 || ^v8.0.0" + "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -278,9 +278,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.366.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.13" }, - "time": "2025-12-08T19:11:08+00:00" + "time": "2026-01-14T19:13:46+00:00" }, { "name": "bacon/bacon-qr-code", @@ -339,25 +339,25 @@ }, { "name": "brick/math", - "version": "0.13.1", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -387,7 +387,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.13.1" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -395,7 +395,7 @@ "type": "github" } ], - "time": "2025-03-29T13:50:30+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -907,16 +907,16 @@ }, { "name": "doctrine/collections", - "version": "2.4.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "9acfeea2e8666536edff3d77c531261c63680160" + "reference": "171e68db4b9aca9dc1f5d49925762f3d53d248c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", - "reference": "9acfeea2e8666536edff3d77c531261c63680160", + "url": "https://api.github.com/repos/doctrine/collections/zipball/171e68db4b9aca9dc1f5d49925762f3d53d248c5", + "reference": "171e68db4b9aca9dc1f5d49925762f3d53d248c5", "shasum": "" }, "require": { @@ -973,7 +973,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.4.0" + "source": "https://github.com/doctrine/collections/tree/2.5.1" }, "funding": [ { @@ -989,7 +989,7 @@ "type": "tidelift" } ], - "time": "2025-10-25T09:18:13+00:00" + "time": "2026-01-12T20:53:55+00:00" }, { "name": "doctrine/dbal", @@ -1155,16 +1155,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.18.1", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "b769877014de053da0e5cbbb63d0ea2f3b2fea76" + "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/b769877014de053da0e5cbbb63d0ea2f3b2fea76", - "reference": "b769877014de053da0e5cbbb63d0ea2f3b2fea76", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546", + "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546", "shasum": "" }, "require": { @@ -1256,7 +1256,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.1" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2" }, "funding": [ { @@ -1272,7 +1272,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:42:10+00:00" + "time": "2025-12-20T21:35:32+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -1792,16 +1792,16 @@ }, { "name": "doctrine/orm", - "version": "3.5.8", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "78dd074266e8b47a83bcf60ab5fe06c91a639168" + "reference": "2148940290e4c44b9101095707e71fb590832fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/78dd074266e8b47a83bcf60ab5fe06c91a639168", - "reference": "78dd074266e8b47a83bcf60ab5fe06c91a639168", + "url": "https://api.github.com/repos/doctrine/orm/zipball/2148940290e4c44b9101095707e71fb590832fa5", + "reference": "2148940290e4c44b9101095707e71fb590832fa5", "shasum": "" }, "require": { @@ -1874,9 +1874,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.5.8" + "source": "https://github.com/doctrine/orm/tree/3.6.1" }, - "time": "2025-11-29T23:11:02+00:00" + "time": "2026-01-09T05:28:15+00:00" }, { "name": "doctrine/persistence", @@ -2393,20 +2393,20 @@ }, { "name": "google/apiclient", - "version": "v2.18.4", + "version": "v2.19.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client.git", - "reference": "5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b" + "reference": "b18fa8aed7b2b2dd4bcce74e2c7d267e16007ea9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b", - "reference": "5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/b18fa8aed7b2b2dd4bcce74e2c7d267e16007ea9", + "reference": "b18fa8aed7b2b2dd4bcce74e2c7d267e16007ea9", "shasum": "" }, "require": { - "firebase/php-jwt": "^6.0", + "firebase/php-jwt": "^6.0||^7.0", "google/apiclient-services": "~0.350", "google/auth": "^1.37", "guzzlehttp/guzzle": "^7.4.5", @@ -2456,22 +2456,22 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client/issues", - "source": "https://github.com/googleapis/google-api-php-client/tree/v2.18.4" + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.19.0" }, - "time": "2025-09-30T04:23:07+00:00" + "time": "2026-01-09T19:59:47+00:00" }, { "name": "google/apiclient-services", - "version": "v0.423.0", + "version": "v0.428.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "c8f2e8a92e2848987db421175eaaf7602ef30c33" + "reference": "94a3c50a80a36cafb76e32fb76b8007e9f572deb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/c8f2e8a92e2848987db421175eaaf7602ef30c33", - "reference": "c8f2e8a92e2848987db421175eaaf7602ef30c33", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/94a3c50a80a36cafb76e32fb76b8007e9f572deb", + "reference": "94a3c50a80a36cafb76e32fb76b8007e9f572deb", "shasum": "" }, "require": { @@ -2500,26 +2500,26 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.423.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.428.0" }, - "time": "2025-12-08T01:10:26+00:00" + "time": "2026-01-12T00:58:26+00:00" }, { "name": "google/auth", - "version": "v1.49.0", + "version": "v1.50.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "68e3d88cb59a49f713e3db25d4f6bb3cc0b70764" + "reference": "e1c26a718198e16d8a3c69b1cae136b73f959b0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/68e3d88cb59a49f713e3db25d4f6bb3cc0b70764", - "reference": "68e3d88cb59a49f713e3db25d4f6bb3cc0b70764", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/e1c26a718198e16d8a3c69b1cae136b73f959b0f", + "reference": "e1c26a718198e16d8a3c69b1cae136b73f959b0f", "shasum": "" }, "require": { - "firebase/php-jwt": "^6.0", + "firebase/php-jwt": "^6.0||^7.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.4.5", "php": "^8.1", @@ -2529,7 +2529,7 @@ }, "require-dev": { "guzzlehttp/promises": "^2.0", - "kelvinmo/simplejwt": "0.7.1", + "kelvinmo/simplejwt": "^1.1.0", "phpseclib/phpseclib": "^3.0.35", "phpspec/prophecy-phpunit": "^2.1", "phpunit/phpunit": "^9.6", @@ -2537,7 +2537,7 @@ "squizlabs/php_codesniffer": "^4.0", "symfony/filesystem": "^6.3||^7.3", "symfony/process": "^6.0||^7.0", - "webmozart/assert": "^1.11" + "webmozart/assert": "^1.11||^2.0" }, "suggest": { "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." @@ -2562,9 +2562,9 @@ "support": { "docs": "https://cloud.google.com/php/docs/reference/auth/latest", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.49.0" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.50.0" }, - "time": "2025-11-06T21:27:55+00:00" + "time": "2026-01-08T21:33:57+00:00" }, { "name": "google/cloud", @@ -3426,16 +3426,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.2", + "version": "v4.33.4", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318" + "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", - "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/22d28025cda0d223a2e48c2e16c5284ecc9f5402", + "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402", "shasum": "" }, "require": { @@ -3464,9 +3464,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.4" }, - "time": "2025-12-05T22:12:22+00:00" + "time": "2026-01-12T17:58:43+00:00" }, { "name": "grpc/grpc", @@ -3877,16 +3877,16 @@ }, { "name": "illuminate/collections", - "version": "v12.42.0", + "version": "v12.47.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "16657effa6a5a4e728f9aeb3e38fb4fa9ba70e7d" + "reference": "d3f104a32fdfbf416cd1a839dad0e2c670a03f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/16657effa6a5a4e728f9aeb3e38fb4fa9ba70e7d", - "reference": "16657effa6a5a4e728f9aeb3e38fb4fa9ba70e7d", + "url": "https://api.github.com/repos/illuminate/collections/zipball/d3f104a32fdfbf416cd1a839dad0e2c670a03f4e", + "reference": "d3f104a32fdfbf416cd1a839dad0e2c670a03f4e", "shasum": "" }, "require": { @@ -3894,6 +3894,7 @@ "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", "php": "^8.2", + "symfony/polyfill-php83": "^1.33", "symfony/polyfill-php84": "^1.33", "symfony/polyfill-php85": "^1.33" }, @@ -3932,11 +3933,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-06T18:08:25+00:00" + "time": "2026-01-07T21:26:29+00:00" }, { "name": "illuminate/conditionable", - "version": "v12.42.0", + "version": "v12.47.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -3982,16 +3983,16 @@ }, { "name": "illuminate/contracts", - "version": "v12.42.0", + "version": "v12.47.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "19e8938edb73047017cfbd443b96844b86da4a59" + "reference": "2c0015e16b40f32c41e49810b6a0acf61204ea3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/19e8938edb73047017cfbd443b96844b86da4a59", - "reference": "19e8938edb73047017cfbd443b96844b86da4a59", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/2c0015e16b40f32c41e49810b6a0acf61204ea3d", + "reference": "2c0015e16b40f32c41e49810b6a0acf61204ea3d", "shasum": "" }, "require": { @@ -4026,11 +4027,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-26T21:36:01+00:00" + "time": "2026-01-07T14:57:06+00:00" }, { "name": "illuminate/macroable", - "version": "v12.42.0", + "version": "v12.47.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -4076,7 +4077,7 @@ }, { "name": "illuminate/reflection", - "version": "v12.42.0", + "version": "v12.47.0", "source": { "type": "git", "url": "https://github.com/illuminate/reflection.git", @@ -4127,16 +4128,16 @@ }, { "name": "illuminate/support", - "version": "v12.42.0", + "version": "v12.47.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "d35411be5657e0b5560a5885f3c9140e4cbe0be5" + "reference": "1757ae693f552f82179ebcdb0a4983a56e645f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/d35411be5657e0b5560a5885f3c9140e4cbe0be5", - "reference": "d35411be5657e0b5560a5885f3c9140e4cbe0be5", + "url": "https://api.github.com/repos/illuminate/support/zipball/1757ae693f552f82179ebcdb0a4983a56e645f27", + "reference": "1757ae693f552f82179ebcdb0a4983a56e645f27", "shasum": "" }, "require": { @@ -4203,20 +4204,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-09T15:26:52+00:00" + "time": "2026-01-13T14:50:24+00:00" }, { "name": "imagine/imagine", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/php-imagine/Imagine.git", - "reference": "8b130cd281efdea67e52d5f0f998572eb62d2f04" + "reference": "f9ed796eefb77c2f0f2167e1d4e36bc2b5ed6b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/8b130cd281efdea67e52d5f0f998572eb62d2f04", - "reference": "8b130cd281efdea67e52d5f0f998572eb62d2f04", + "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/f9ed796eefb77c2f0f2167e1d4e36bc2b5ed6b0c", + "reference": "f9ed796eefb77c2f0f2167e1d4e36bc2b5ed6b0c", "shasum": "" }, "require": { @@ -4263,9 +4264,9 @@ ], "support": { "issues": "https://github.com/php-imagine/Imagine/issues", - "source": "https://github.com/php-imagine/Imagine/tree/1.5.1" + "source": "https://github.com/php-imagine/Imagine/tree/1.5.2" }, - "time": "2025-12-09T15:27:47+00:00" + "time": "2026-01-09T10:45:12+00:00" }, { "name": "intervention/image", @@ -4701,6 +4702,66 @@ }, "time": "2025-11-29T09:14:09+00:00" }, + { + "name": "knpuniversity/oauth2-client-bundle", + "version": "v2.20.1", + "source": { + "type": "git", + "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", + "reference": "d59e4dc61484e777b6f19df2efcf8b1bcc03828a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/d59e4dc61484e777b6f19df2efcf8b1bcc03828a", + "reference": "d59e4dc61484e777b6f19df2efcf8b1bcc03828a", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0", + "php": ">=8.1", + "symfony/dependency-injection": "^6.4|^7.3|^8.0", + "symfony/framework-bundle": "^6.4|^7.3|^8.0", + "symfony/http-foundation": "^6.4|^7.3|^8.0", + "symfony/routing": "^6.4|^7.3|^8.0", + "symfony/security-core": "^6.4|^7.3|^8.0", + "symfony/security-http": "^6.4|^7.3|^8.0" + }, + "require-dev": { + "league/oauth2-facebook": "^1.1|^2.0", + "symfony/phpunit-bridge": "^7.3", + "symfony/yaml": "^6.4|^7.3|^8.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.20.1" + }, + "time": "2025-12-04T15:46:43+00:00" + }, { "name": "lasserafn/php-initial-avatar-generator", "version": "4.5", @@ -5002,16 +5063,16 @@ }, { "name": "league/flysystem-bundle", - "version": "3.6.0", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-bundle.git", - "reference": "88376670b35f11846bed7dca2efb82d993be22d0" + "reference": "61e7f989f14080abb14d5ade80629303691555cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/88376670b35f11846bed7dca2efb82d993be22d0", - "reference": "88376670b35f11846bed7dca2efb82d993be22d0", + "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/61e7f989f14080abb14d5ade80629303691555cf", + "reference": "61e7f989f14080abb14d5ade80629303691555cf", "shasum": "" }, "require": { @@ -5066,9 +5127,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-bundle/issues", - "source": "https://github.com/thephpleague/flysystem-bundle/tree/3.6.0" + "source": "https://github.com/thephpleague/flysystem-bundle/tree/3.6.1" }, - "time": "2025-08-21T17:25:13+00:00" + "time": "2025-12-11T15:40:58+00:00" }, { "name": "league/flysystem-local", @@ -5452,16 +5513,16 @@ }, { "name": "maennchen/zipstream-php", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", "shasum": "" }, "require": { @@ -5472,7 +5533,7 @@ "require-dev": { "brianium/paratest": "^7.7", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.16", + "friendsofphp/php-cs-fixer": "^3.86", "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", @@ -5518,7 +5579,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" }, "funding": [ { @@ -5526,7 +5587,7 @@ "type": "github" } ], - "time": "2025-07-17T11:15:13+00:00" + "time": "2025-12-10T09:58:31+00:00" }, { "name": "markbaker/complex", @@ -5687,16 +5748,16 @@ }, { "name": "minishlink/web-push", - "version": "v9.0.3", + "version": "v9.0.4", "source": { "type": "git", "url": "https://github.com/web-push-libs/web-push-php.git", - "reference": "5c185f78ee41f271e2ea7314c80760040465b713" + "reference": "f979f40b0017d2f86d82b9f21edbc515d031cc23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/5c185f78ee41f271e2ea7314c80760040465b713", - "reference": "5c185f78ee41f271e2ea7314c80760040465b713", + "url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/f979f40b0017d2f86d82b9f21edbc515d031cc23", + "reference": "f979f40b0017d2f86d82b9f21edbc515d031cc23", "shasum": "" }, "require": { @@ -5704,15 +5765,17 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/guzzle": "^7.9.2", "php": ">=8.1", "spomky-labs/base64url": "^2.0.4", + "symfony/polyfill-php82": "^v1.31.0", "web-token/jwt-library": "^3.3.0|^4.0.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.68.5", + "friendsofphp/php-cs-fixer": "^v3.91.3", "phpstan/phpstan": "^2.1.2", - "phpunit/phpunit": "^10.5.44|^11.5.6" + "phpunit/phpunit": "^10.5.44|^11.5.6", + "symfony/polyfill-iconv": "^1.33" }, "suggest": { "ext-bcmath": "Optional for performance.", @@ -5746,9 +5809,9 @@ ], "support": { "issues": "https://github.com/web-push-libs/web-push-php/issues", - "source": "https://github.com/web-push-libs/web-push-php/tree/v9.0.3" + "source": "https://github.com/web-push-libs/web-push-php/tree/v9.0.4" }, - "time": "2025-11-13T17:14:30+00:00" + "time": "2025-12-10T14:00:12+00:00" }, { "name": "mittwald/vault-php", @@ -5804,29 +5867,28 @@ }, { "name": "mobiledetect/mobiledetectlib", - "version": "4.8.09", + "version": "4.8.10", "source": { "type": "git", "url": "https://github.com/serbanghita/Mobile-Detect.git", - "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209" + "reference": "96b1e1fa9a968de7660a031106ab529f659d0192" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/a06fe2e546a06bb8c2639d6823d5250b2efb3209", - "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/96b1e1fa9a968de7660a031106ab529f659d0192", + "reference": "96b1e1fa9a968de7660a031106ab529f659d0192", "shasum": "" }, "require": { "php": ">=8.0", - "psr/cache": "^3.0", "psr/simple-cache": "^3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.65.0", + "friendsofphp/php-cs-fixer": "^v3.75.0", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.12.x-dev", - "phpunit/phpunit": "^9.6.18", - "squizlabs/php_codesniffer": "^3.11.1" + "phpstan/phpstan": "^2.1.11", + "phpunit/phpunit": "^9.6.22", + "squizlabs/php_codesniffer": "^3.12.1" }, "type": "library", "autoload": { @@ -5857,7 +5919,7 @@ ], "support": { "issues": "https://github.com/serbanghita/Mobile-Detect/issues", - "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.09" + "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.10" }, "funding": [ { @@ -5865,20 +5927,20 @@ "type": "github" } ], - "time": "2024-12-10T15:32:06+00:00" + "time": "2026-01-09T16:21:59+00:00" }, { "name": "monolog/monolog", - "version": "3.9.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { @@ -5896,7 +5958,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.8", "phpstan/phpstan": "^2", @@ -5956,7 +6018,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -5968,7 +6030,7 @@ "type": "tidelift" } ], - "time": "2025-03-24T10:02:05+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { "name": "mtdowling/jmespath.php", @@ -6038,16 +6100,16 @@ }, { "name": "nelmio/cors-bundle", - "version": "2.6.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioCorsBundle.git", - "reference": "530217472204881cacd3671909f634b960c7b948" + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/530217472204881cacd3671909f634b960c7b948", - "reference": "530217472204881cacd3671909f634b960c7b948", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", "shasum": "" }, "require": { @@ -6097,9 +6159,9 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", - "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.0" + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.1" }, - "time": "2025-10-23T06:57:22+00:00" + "time": "2026-01-12T15:59:08+00:00" }, { "name": "nesbot/carbon", @@ -6567,16 +6629,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.5", + "version": "5.6.6", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761" + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90614c73d3800e187615e2dd236ad0e2a01bf761", - "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", "shasum": "" }, "require": { @@ -6586,7 +6648,7 @@ "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1" + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { "mockery/mockery": "~1.3.5 || ~1.6.0", @@ -6625,9 +6687,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.5" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" }, - "time": "2025-11-27T19:50:05+00:00" + "time": "2025-12-22T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -6689,16 +6751,16 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "5.3.0", + "version": "5.4.0", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df" + "reference": "48f2fe37d64c2dece0ef71fb2ac55497566782af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/4d597c1aacdde1805a33c525b9758113ea0d90df", - "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/48f2fe37d64c2dece0ef71fb2ac55497566782af", + "reference": "48f2fe37d64c2dece0ef71fb2ac55497566782af", "shasum": "" }, "require": { @@ -6706,6 +6768,7 @@ "ext-ctype": "*", "ext-dom": "*", "ext-fileinfo": "*", + "ext-filter": "*", "ext-gd": "*", "ext-iconv": "*", "ext-libxml": "*", @@ -6720,13 +6783,12 @@ "markbaker/complex": "^3.0", "markbaker/matrix": "^3.0", "php": "^8.1", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "dev-main", "dompdf/dompdf": "^2.0 || ^3.0", + "ext-intl": "*", "friendsofphp/php-cs-fixer": "^3.2", "mitoteam/jpgraph": "^10.5", "mpdf/mpdf": "^8.1.1", @@ -6740,7 +6802,7 @@ }, "suggest": { "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard", + "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard and StringHelper::setLocale()", "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", "mpdf/mpdf": "Option for rendering PDF with PDF Writer", "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" @@ -6773,6 +6835,9 @@ }, { "name": "Adrien Crivelli" + }, + { + "name": "Owen Leibman" } ], "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", @@ -6789,22 +6854,22 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.3.0" + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.4.0" }, - "time": "2025-11-24T15:47:10+00:00" + "time": "2026-01-11T04:52:00+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.47", + "version": "3.0.48", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" + "reference": "64065a5679c50acb886e82c07aa139b0f757bb89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", - "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89", + "reference": "64065a5679c50acb886e82c07aa139b0f757bb89", "shasum": "" }, "require": { @@ -6885,7 +6950,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.48" }, "funding": [ { @@ -6901,20 +6966,20 @@ "type": "tidelift" } ], - "time": "2025-10-06T01:07:24+00:00" + "time": "2025-12-15T11:51:42+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/16dbf9937da8d4528ceb2145c9c7c0bd29e26374", + "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374", "shasum": "" }, "require": { @@ -6946,9 +7011,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.1" }, - "time": "2025-08-30T15:50:23+00:00" + "time": "2026-01-12T11:33:04+00:00" }, { "name": "presta/sitemap-bundle", @@ -7657,20 +7722,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -7729,9 +7794,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "time": "2025-09-04T20:59:21+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "rize/uri-template", @@ -7887,16 +7952,16 @@ }, { "name": "sentry/sentry-symfony", - "version": "5.8.2", + "version": "5.8.3", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-symfony.git", - "reference": "fb03d506c575cd1cb5274774d0dd968938150982" + "reference": "e82559a078b26c8f8592289e98a25b203527a9c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/fb03d506c575cd1cb5274774d0dd968938150982", - "reference": "fb03d506c575cd1cb5274774d0dd968938150982", + "url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/e82559a078b26c8f8592289e98a25b203527a9c6", + "reference": "e82559a078b26c8f8592289e98a25b203527a9c6", "shasum": "" }, "require": { @@ -7973,7 +8038,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-symfony/issues", - "source": "https://github.com/getsentry/sentry-symfony/tree/5.8.2" + "source": "https://github.com/getsentry/sentry-symfony/tree/5.8.3" }, "funding": [ { @@ -7985,7 +8050,7 @@ "type": "custom" } ], - "time": "2025-12-04T12:34:17+00:00" + "time": "2025-12-18T09:26:49+00:00" }, { "name": "setasign/fpdi", @@ -8258,16 +8323,16 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7" + "reference": "f0e9a548df4e3942886adc9b7830581a46334631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7", - "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/f0e9a548df4e3942886adc9b7830581a46334631", + "reference": "f0e9a548df4e3942886adc9b7830581a46334631", "shasum": "" }, "require": { @@ -8351,7 +8416,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.1" }, "funding": [ { @@ -8363,7 +8428,7 @@ "type": "patreon" } ], - "time": "2025-10-22T08:24:34+00:00" + "time": "2025-12-20T12:57:40+00:00" }, { "name": "stancer/stancer", @@ -8429,6 +8494,67 @@ }, "time": "2024-11-15T17:47:59+00:00" }, + { + "name": "stevenmaguire/oauth2-keycloak", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", + "reference": "1b690b7377dfe7a23e1590373f37e12cf40a6d75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/1b690b7377dfe7a23e1590373f37e12cf40a6d75", + "reference": "1b690b7377dfe7a23e1590373f37e12cf40a6d75", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0", + "league/oauth2-client": "^2.0", + "php": "~7.2 || ~8.0" + }, + "require-dev": { + "mockery/mockery": "~1.5.0", + "phpunit/phpunit": "~9.6.4", + "squizlabs/php_codesniffer": "~3.7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stevenmaguire\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "keycloak", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/5.1.0" + }, + "time": "2023-10-24T06:10:44+00:00" + }, { "name": "symfony/amazon-mailer", "version": "v7.3.0", @@ -8566,16 +8692,16 @@ }, { "name": "symfony/asset-mapper", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/asset-mapper.git", - "reference": "d66dba9dbc1b75289ed3dc45664794891ae881cb" + "reference": "781da831b7e4eaa39c904958f9446c829fa21a6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/d66dba9dbc1b75289ed3dc45664794891ae881cb", - "reference": "d66dba9dbc1b75289ed3dc45664794891ae881cb", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/781da831b7e4eaa39c904958f9446c829fa21a6d", + "reference": "781da831b7e4eaa39c904958f9446c829fa21a6d", "shasum": "" }, "require": { @@ -8626,7 +8752,7 @@ "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset-mapper/tree/v7.3.8" + "source": "https://github.com/symfony/asset-mapper/tree/v7.3.9" }, "funding": [ { @@ -8646,20 +8772,20 @@ "type": "tidelift" } ], - "time": "2025-11-21T13:14:48+00:00" + "time": "2025-12-19T08:58:15+00:00" }, { "name": "symfony/cache", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "288ea9853bbf6b395ee09bde9ac6da415fffbc8c" + "reference": "fae016390a2e6b451abc0f4805cbfe0bf70f437f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/288ea9853bbf6b395ee09bde9ac6da415fffbc8c", - "reference": "288ea9853bbf6b395ee09bde9ac6da415fffbc8c", + "url": "https://api.github.com/repos/symfony/cache/zipball/fae016390a2e6b451abc0f4805cbfe0bf70f437f", + "reference": "fae016390a2e6b451abc0f4805cbfe0bf70f437f", "shasum": "" }, "require": { @@ -8728,7 +8854,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.8" + "source": "https://github.com/symfony/cache/tree/v7.3.9" }, "funding": [ { @@ -8748,7 +8874,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:07:52+00:00" + "time": "2025-12-28T10:45:15+00:00" }, { "name": "symfony/cache-contracts", @@ -8985,16 +9111,16 @@ }, { "name": "symfony/console", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6d0d25cc1138bb7bab0685fbe4184e6289914406" + "reference": "3fe62811ba48799ae958208b55674abbc8796b71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6d0d25cc1138bb7bab0685fbe4184e6289914406", - "reference": "6d0d25cc1138bb7bab0685fbe4184e6289914406", + "url": "https://api.github.com/repos/symfony/console/zipball/3fe62811ba48799ae958208b55674abbc8796b71", + "reference": "3fe62811ba48799ae958208b55674abbc8796b71", "shasum": "" }, "require": { @@ -9059,7 +9185,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.8" + "source": "https://github.com/symfony/console/tree/v7.3.9" }, "funding": [ { @@ -9079,20 +9205,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:21+00:00" + "time": "2025-12-23T14:45:27+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "6d83d997230971a350bfb0b9bfc43e8dead5ff9a" + "reference": "53a1c925df19dc3a67eb124fc5dc980a09f5d971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6d83d997230971a350bfb0b9bfc43e8dead5ff9a", - "reference": "6d83d997230971a350bfb0b9bfc43e8dead5ff9a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/53a1c925df19dc3a67eb124fc5dc980a09f5d971", + "reference": "53a1c925df19dc3a67eb124fc5dc980a09f5d971", "shasum": "" }, "require": { @@ -9143,7 +9269,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.3.8" + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.9" }, "funding": [ { @@ -9163,7 +9289,7 @@ "type": "tidelift" } ], - "time": "2025-12-07T09:35:41+00:00" + "time": "2025-12-28T10:53:28+00:00" }, { "name": "symfony/deprecation-contracts", @@ -9347,16 +9473,16 @@ }, { "name": "symfony/doctrine-messenger", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-messenger.git", - "reference": "f5d6c8ea82b9378221186393e8dff9c32ea09d10" + "reference": "ed215e61df79c1e504b2790ebdf68a6110a5e526" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/f5d6c8ea82b9378221186393e8dff9c32ea09d10", - "reference": "f5d6c8ea82b9378221186393e8dff9c32ea09d10", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/ed215e61df79c1e504b2790ebdf68a6110a5e526", + "reference": "ed215e61df79c1e504b2790ebdf68a6110a5e526", "shasum": "" }, "require": { @@ -9399,7 +9525,7 @@ "description": "Symfony Doctrine Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-messenger/tree/v7.3.8" + "source": "https://github.com/symfony/doctrine-messenger/tree/v7.3.9" }, "funding": [ { @@ -9419,7 +9545,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:21+00:00" + "time": "2025-12-16T07:50:38+00:00" }, { "name": "symfony/dotenv", @@ -9880,16 +10006,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.5", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f" + "reference": "197abc62f8d8537dd9f75d1bfd307ec24551d994" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f", + "url": "https://api.github.com/repos/symfony/finder/zipball/197abc62f8d8537dd9f75d1bfd307ec24551d994", + "reference": "197abc62f8d8537dd9f75d1bfd307ec24551d994", "shasum": "" }, "require": { @@ -9924,7 +10050,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.5" + "source": "https://github.com/symfony/finder/tree/v7.3.9" }, "funding": [ { @@ -9944,7 +10070,7 @@ "type": "tidelift" } ], - "time": "2025-10-15T18:45:57+00:00" + "time": "2025-12-23T14:45:27+00:00" }, { "name": "symfony/flex", @@ -10021,16 +10147,16 @@ }, { "name": "symfony/form", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "008a7b331d7f42ac68f8f979566000f1aa0fbe0c" + "reference": "3e3299150a8a9ba080c93dc3af62e1a38fcb77b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/008a7b331d7f42ac68f8f979566000f1aa0fbe0c", - "reference": "008a7b331d7f42ac68f8f979566000f1aa0fbe0c", + "url": "https://api.github.com/repos/symfony/form/zipball/3e3299150a8a9ba080c93dc3af62e1a38fcb77b7", + "reference": "3e3299150a8a9ba080c93dc3af62e1a38fcb77b7", "shasum": "" }, "require": { @@ -10098,7 +10224,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.3.8" + "source": "https://github.com/symfony/form/tree/v7.3.9" }, "funding": [ { @@ -10118,20 +10244,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:21+00:00" + "time": "2025-12-23T14:45:27+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "5d2e60f301dbafba1408e62b9838fdb58920c2ca" + "reference": "aeae70599abfc530a787b2ef091f3ab89d7a2a80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/5d2e60f301dbafba1408e62b9838fdb58920c2ca", - "reference": "5d2e60f301dbafba1408e62b9838fdb58920c2ca", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/aeae70599abfc530a787b2ef091f3ab89d7a2a80", + "reference": "aeae70599abfc530a787b2ef091f3ab89d7a2a80", "shasum": "" }, "require": { @@ -10256,7 +10382,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.3.8" + "source": "https://github.com/symfony/framework-bundle/tree/v7.3.9" }, "funding": [ { @@ -10276,20 +10402,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:40+00:00" + "time": "2025-12-23T14:45:27+00:00" }, { "name": "symfony/http-client", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "3d125854b2f254303d7dfd1d3af15954ac65703b" + "reference": "7978f8fcbd86fca55d02f9cd1428a9f8b8cf5d06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/3d125854b2f254303d7dfd1d3af15954ac65703b", - "reference": "3d125854b2f254303d7dfd1d3af15954ac65703b", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7978f8fcbd86fca55d02f9cd1428a9f8b8cf5d06", + "reference": "7978f8fcbd86fca55d02f9cd1428a9f8b8cf5d06", "shasum": "" }, "require": { @@ -10356,7 +10482,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.8" + "source": "https://github.com/symfony/http-client/tree/v7.3.9" }, "funding": [ { @@ -10376,7 +10502,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:07:52+00:00" + "time": "2025-12-23T14:45:27+00:00" }, { "name": "symfony/http-client-contracts", @@ -10458,16 +10584,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "8cdae4e108673e0d3e4f18ef2ee79ff5023beeac" + "reference": "6dc98931a559065ff8f968ae0e461e600a321291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8cdae4e108673e0d3e4f18ef2ee79ff5023beeac", - "reference": "8cdae4e108673e0d3e4f18ef2ee79ff5023beeac", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6dc98931a559065ff8f968ae0e461e600a321291", + "reference": "6dc98931a559065ff8f968ae0e461e600a321291", "shasum": "" }, "require": { @@ -10517,7 +10643,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.8" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.9" }, "funding": [ { @@ -10537,20 +10663,20 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:07:52+00:00" + "time": "2025-12-19T08:58:15+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b4bfe6980782b89a2f9c78e4a0f00f0582c8043e" + "reference": "b319fe1248b4df79adb7b2b1f0954fdecf08f0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b4bfe6980782b89a2f9c78e4a0f00f0582c8043e", - "reference": "b4bfe6980782b89a2f9c78e4a0f00f0582c8043e", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b319fe1248b4df79adb7b2b1f0954fdecf08f0b2", + "reference": "b319fe1248b4df79adb7b2b1f0954fdecf08f0b2", "shasum": "" }, "require": { @@ -10635,7 +10761,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.8" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.9" }, "funding": [ { @@ -10655,7 +10781,7 @@ "type": "tidelift" } ], - "time": "2025-12-07T16:03:07+00:00" + "time": "2025-12-31T08:34:55+00:00" }, { "name": "symfony/intl", @@ -10749,16 +10875,16 @@ }, { "name": "symfony/mailer", - "version": "v7.3.5", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" + "reference": "efd8b9875358612a5d80a02bab4d63c312d8efe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", - "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", + "url": "https://api.github.com/repos/symfony/mailer/zipball/efd8b9875358612a5d80a02bab4d63c312d8efe4", + "reference": "efd8b9875358612a5d80a02bab4d63c312d8efe4", "shasum": "" }, "require": { @@ -10809,7 +10935,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.5" + "source": "https://github.com/symfony/mailer/tree/v7.3.9" }, "funding": [ { @@ -10829,20 +10955,20 @@ "type": "tidelift" } ], - "time": "2025-10-24T14:27:20+00:00" + "time": "2025-12-16T07:50:38+00:00" }, { "name": "symfony/messenger", - "version": "v7.3.6", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "58a7efa3bebadbe4cdd8f7577c5856f0e3ea3978" + "reference": "43cf1fcc37adfee14f5ef8eb0ca1440554e98ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/58a7efa3bebadbe4cdd8f7577c5856f0e3ea3978", - "reference": "58a7efa3bebadbe4cdd8f7577c5856f0e3ea3978", + "url": "https://api.github.com/repos/symfony/messenger/zipball/43cf1fcc37adfee14f5ef8eb0ca1440554e98ec5", + "reference": "43cf1fcc37adfee14f5ef8eb0ca1440554e98ec5", "shasum": "" }, "require": { @@ -10902,7 +11028,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v7.3.6" + "source": "https://github.com/symfony/messenger/tree/v7.3.9" }, "funding": [ { @@ -10922,7 +11048,7 @@ "type": "tidelift" } ], - "time": "2025-11-06T11:17:34+00:00" + "time": "2025-12-18T08:25:32+00:00" }, { "name": "symfony/mime", @@ -12151,16 +12277,16 @@ }, { "name": "symfony/process", - "version": "v7.3.4", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" + "reference": "cbfa8595e86911b7c9dcd6e80e2205e82be86180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", + "url": "https://api.github.com/repos/symfony/process/zipball/cbfa8595e86911b7c9dcd6e80e2205e82be86180", + "reference": "cbfa8595e86911b7c9dcd6e80e2205e82be86180", "shasum": "" }, "require": { @@ -12192,7 +12318,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.4" + "source": "https://github.com/symfony/process/tree/v7.3.9" }, "funding": [ { @@ -12212,25 +12338,25 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-12-19T08:58:15+00:00" }, { "name": "symfony/property-access", - "version": "v7.3.3", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7" + "reference": "fa254c8f0be6423281822cefa6a81ef59c10b8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", - "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "url": "https://api.github.com/repos/symfony/property-access/zipball/fa254c8f0be6423281822cefa6a81ef59c10b8ec", + "reference": "fa254c8f0be6423281822cefa6a81ef59c10b8ec", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/property-info": "^6.4|^7.0" + "symfony/property-info": "^6.4.31|~7.3.9|^7.4.2" }, "require-dev": { "symfony/cache": "^6.4|^7.0" @@ -12272,7 +12398,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.3.3" + "source": "https://github.com/symfony/property-access/tree/v7.3.9" }, "funding": [ { @@ -12292,20 +12418,20 @@ "type": "tidelift" } ], - "time": "2025-08-04T15:15:28+00:00" + "time": "2025-12-18T10:35:05+00:00" }, { "name": "symfony/property-info", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "3a0f08e10916364a02780181eb9c2269be114044" + "reference": "243a05bc1d8cc73df8ed19d29d76aeec7b930677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/3a0f08e10916364a02780181eb9c2269be114044", - "reference": "3a0f08e10916364a02780181eb9c2269be114044", + "url": "https://api.github.com/repos/symfony/property-info/zipball/243a05bc1d8cc73df8ed19d29d76aeec7b930677", + "reference": "243a05bc1d8cc73df8ed19d29d76aeec7b930677", "shasum": "" }, "require": { @@ -12362,7 +12488,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.3.8" + "source": "https://github.com/symfony/property-info/tree/v7.3.9" }, "funding": [ { @@ -12382,7 +12508,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:40+00:00" + "time": "2025-12-18T08:25:32+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -12544,16 +12670,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "7350aebf3d01e41c0a13245dd6008ace8780b3bb" + "reference": "de7849b54c6a6f2a5fe1c761639e549eb81a5089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7350aebf3d01e41c0a13245dd6008ace8780b3bb", - "reference": "7350aebf3d01e41c0a13245dd6008ace8780b3bb", + "url": "https://api.github.com/repos/symfony/routing/zipball/de7849b54c6a6f2a5fe1c761639e549eb81a5089", + "reference": "de7849b54c6a6f2a5fe1c761639e549eb81a5089", "shasum": "" }, "require": { @@ -12605,7 +12731,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.8" + "source": "https://github.com/symfony/routing/tree/v7.3.9" }, "funding": [ { @@ -12625,7 +12751,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T15:55:45+00:00" + "time": "2025-12-16T20:27:23+00:00" }, { "name": "symfony/runtime", @@ -12822,16 +12948,16 @@ }, { "name": "symfony/security-core", - "version": "v7.3.5", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "772a7c1eddd8bf8a977a67e6e8adc59650c604eb" + "reference": "dcd462202eb3ced09edf610926eb469b9180c33f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/772a7c1eddd8bf8a977a67e6e8adc59650c604eb", - "reference": "772a7c1eddd8bf8a977a67e6e8adc59650c604eb", + "url": "https://api.github.com/repos/symfony/security-core/zipball/dcd462202eb3ced09edf610926eb469b9180c33f", + "reference": "dcd462202eb3ced09edf610926eb469b9180c33f", "shasum": "" }, "require": { @@ -12889,7 +13015,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.3.5" + "source": "https://github.com/symfony/security-core/tree/v7.3.9" }, "funding": [ { @@ -12909,20 +13035,20 @@ "type": "tidelift" } ], - "time": "2025-10-24T14:27:20+00:00" + "time": "2025-12-19T11:33:01+00:00" }, { "name": "symfony/security-csrf", - "version": "v7.3.0", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3" + "reference": "ce032a98fd6cdef964e932cc9d7f38cb2b2b9035" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/2b4b0c46c901729e4e90719eacd980381f53e0a3", - "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/ce032a98fd6cdef964e932cc9d7f38cb2b2b9035", + "reference": "ce032a98fd6cdef964e932cc9d7f38cb2b2b9035", "shasum": "" }, "require": { @@ -12963,7 +13089,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v7.3.0" + "source": "https://github.com/symfony/security-csrf/tree/v7.3.9" }, "funding": [ { @@ -12974,25 +13100,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-02T18:42:10+00:00" + "time": "2025-12-23T15:22:52+00:00" }, { "name": "symfony/security-http", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "64b65f2e35d5443a750ac7729652f4b6676a941b" + "reference": "61efc01cf3dad84a436b17d95b44027a8b7c6c41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/64b65f2e35d5443a750ac7729652f4b6676a941b", - "reference": "64b65f2e35d5443a750ac7729652f4b6676a941b", + "url": "https://api.github.com/repos/symfony/security-http/zipball/61efc01cf3dad84a436b17d95b44027a8b7c6c41", + "reference": "61efc01cf3dad84a436b17d95b44027a8b7c6c41", "shasum": "" }, "require": { @@ -13051,7 +13181,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.3.8" + "source": "https://github.com/symfony/security-http/tree/v7.3.9" }, "funding": [ { @@ -13071,20 +13201,20 @@ "type": "tidelift" } ], - "time": "2025-11-23T02:26:15+00:00" + "time": "2025-12-19T11:33:01+00:00" }, { "name": "symfony/serializer", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "e9f668bb3e69cc43571ddd9c2578fe442b6bc632" + "reference": "e6769b126ea7f9668beea94f68fbaf4ed88772f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/e9f668bb3e69cc43571ddd9c2578fe442b6bc632", - "reference": "e9f668bb3e69cc43571ddd9c2578fe442b6bc632", + "url": "https://api.github.com/repos/symfony/serializer/zipball/e6769b126ea7f9668beea94f68fbaf4ed88772f6", + "reference": "e6769b126ea7f9668beea94f68fbaf4ed88772f6", "shasum": "" }, "require": { @@ -13154,7 +13284,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.3.8" + "source": "https://github.com/symfony/serializer/tree/v7.3.9" }, "funding": [ { @@ -13174,7 +13304,7 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:21:00+00:00" + "time": "2025-12-23T14:45:27+00:00" }, { "name": "symfony/service-contracts", @@ -13417,16 +13547,16 @@ }, { "name": "symfony/translation", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "c586b151e8e06987d905679a11f1dd5cc5bc562b" + "reference": "b9bcef6c99cc63f67c2dd0603e7f55db72c7ed3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/c586b151e8e06987d905679a11f1dd5cc5bc562b", - "reference": "c586b151e8e06987d905679a11f1dd5cc5bc562b", + "url": "https://api.github.com/repos/symfony/translation/zipball/b9bcef6c99cc63f67c2dd0603e7f55db72c7ed3a", + "reference": "b9bcef6c99cc63f67c2dd0603e7f55db72c7ed3a", "shasum": "" }, "require": { @@ -13493,7 +13623,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.8" + "source": "https://github.com/symfony/translation/tree/v7.3.9" }, "funding": [ { @@ -13513,7 +13643,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T15:55:45+00:00" + "time": "2025-12-19T11:33:01+00:00" }, { "name": "symfony/translation-contracts", @@ -13599,16 +13729,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "2e9f47a405989f8a543f94160c0d530379b51510" + "reference": "accabb095e9d546ee045f4ff8f013bd7d78ff540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/2e9f47a405989f8a543f94160c0d530379b51510", - "reference": "2e9f47a405989f8a543f94160c0d530379b51510", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/accabb095e9d546ee045f4ff8f013bd7d78ff540", + "reference": "accabb095e9d546ee045f4ff8f013bd7d78ff540", "shasum": "" }, "require": { @@ -13690,7 +13820,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.3.8" + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.9" }, "funding": [ { @@ -13710,20 +13840,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:21+00:00" + "time": "2025-12-16T07:50:38+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.3.4", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "da5c778a8416fcce5318737c4d944f6fa2bb3f81" + "reference": "67ce9929f47bd5875ff04317ac8f5b097b0a2990" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/da5c778a8416fcce5318737c4d944f6fa2bb3f81", - "reference": "da5c778a8416fcce5318737c4d944f6fa2bb3f81", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/67ce9929f47bd5875ff04317ac8f5b097b0a2990", + "reference": "67ce9929f47bd5875ff04317ac8f5b097b0a2990", "shasum": "" }, "require": { @@ -13778,7 +13908,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.3.4" + "source": "https://github.com/symfony/twig-bundle/tree/v7.3.9" }, "funding": [ { @@ -13798,7 +13928,7 @@ "type": "tidelift" } ], - "time": "2025-09-10T12:00:31+00:00" + "time": "2025-12-19T08:58:15+00:00" }, { "name": "symfony/type-info", @@ -13959,16 +14089,16 @@ }, { "name": "symfony/validator", - "version": "v7.3.8", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "f30a6aba4a09d5b5042e06d183ef248e14482313" + "reference": "344efc1f9af111e2a3aaca8990118fd231fb1412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/f30a6aba4a09d5b5042e06d183ef248e14482313", - "reference": "f30a6aba4a09d5b5042e06d183ef248e14482313", + "url": "https://api.github.com/repos/symfony/validator/zipball/344efc1f9af111e2a3aaca8990118fd231fb1412", + "reference": "344efc1f9af111e2a3aaca8990118fd231fb1412", "shasum": "" }, "require": { @@ -14037,7 +14167,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.3.8" + "source": "https://github.com/symfony/validator/tree/v7.3.9" }, "funding": [ { @@ -14057,7 +14187,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:52:21+00:00" + "time": "2025-12-27T17:05:10+00:00" }, { "name": "symfony/var-dumper", @@ -14459,16 +14589,16 @@ }, { "name": "twig/extra-bundle", - "version": "v3.22.1", + "version": "v3.22.2", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "b6534bc925bec930004facca92fccebd0c809247" + "reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/b6534bc925bec930004facca92fccebd0c809247", - "reference": "b6534bc925bec930004facca92fccebd0c809247", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e", + "reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e", "shasum": "" }, "require": { @@ -14517,7 +14647,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.1" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.2" }, "funding": [ { @@ -14529,7 +14659,7 @@ "type": "tidelift" } ], - "time": "2025-11-02T11:00:49+00:00" + "time": "2025-12-05T08:51:53+00:00" }, { "name": "twig/intl-extra", @@ -14597,16 +14727,16 @@ }, { "name": "twig/twig", - "version": "v3.22.1", + "version": "v3.22.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "1de2ec1fc43ab58a4b7e80b214b96bfc895750f3" + "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/1de2ec1fc43ab58a4b7e80b214b96bfc895750f3", - "reference": "1de2ec1fc43ab58a4b7e80b214b96bfc895750f3", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2", + "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2", "shasum": "" }, "require": { @@ -14660,7 +14790,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.22.1" + "source": "https://github.com/twigphp/Twig/tree/v3.22.2" }, "funding": [ { @@ -14672,20 +14802,20 @@ "type": "tidelift" } ], - "time": "2025-11-16T16:01:12+00:00" + "time": "2025-12-14T11:28:47+00:00" }, { "name": "vich/uploader-bundle", - "version": "v2.9.0", + "version": "v2.9.1", "source": { "type": "git", "url": "https://github.com/dustin10/VichUploaderBundle.git", - "reference": "deb1d70a46f3d4250801e8fa40bd52ffa3494d70" + "reference": "945939a04a33c0b78c5fbb7ead31533d85112df5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/deb1d70a46f3d4250801e8fa40bd52ffa3494d70", - "reference": "deb1d70a46f3d4250801e8fa40bd52ffa3494d70", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/945939a04a33c0b78c5fbb7ead31533d85112df5", + "reference": "945939a04a33c0b78c5fbb7ead31533d85112df5", "shasum": "" }, "require": { @@ -14778,9 +14908,9 @@ ], "support": { "issues": "https://github.com/dustin10/VichUploaderBundle/issues", - "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.9.0" + "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.9.1" }, - "time": "2025-12-07T13:46:23+00:00" + "time": "2025-12-10T08:23:38+00:00" }, { "name": "voku/portable-ascii", @@ -14858,43 +14988,32 @@ }, { "name": "web-auth/cose-lib", - "version": "4.4.2", + "version": "4.5.0", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "a93b61c48fb587855f64a9ec11ad7b60e867cb15" + "reference": "5adac6fe126994a3ee17ed9950efb4947ab132a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/a93b61c48fb587855f64a9ec11ad7b60e867cb15", - "reference": "a93b61c48fb587855f64a9ec11ad7b60e867cb15", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/5adac6fe126994a3ee17ed9950efb4947ab132a9", + "reference": "5adac6fe126994a3ee17ed9950efb4947ab132a9", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", "ext-json": "*", "ext-openssl": "*", "php": ">=8.1", "spomky-labs/pki-framework": "^1.0" }, "require-dev": { - "deptrac/deptrac": "^3.0", - "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", - "infection/infection": "^0.29", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/extension-installer": "^1.3", - "phpstan/phpstan": "^1.7|^2.0", - "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", - "phpstan/phpstan-phpunit": "^1.1|^2.0", - "phpstan/phpstan-strict-rules": "^1.0|^2.0", - "phpunit/phpunit": "^10.1|^11.0|^12.0", - "rector/rector": "^2.0", - "symfony/phpunit-bridge": "^6.4|^7.0", - "symplify/easy-coding-standard": "^12.0" + "spomky-labs/cbor-php": "^3.2.2" }, "suggest": { "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension", - "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension" + "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension", + "spomky-labs/cbor-php": "For COSE Signature support" }, "type": "library", "autoload": { @@ -14924,7 +15043,7 @@ ], "support": { "issues": "https://github.com/web-auth/cose-lib/issues", - "source": "https://github.com/web-auth/cose-lib/tree/4.4.2" + "source": "https://github.com/web-auth/cose-lib/tree/4.5.0" }, "funding": [ { @@ -14936,20 +15055,20 @@ "type": "patreon" } ], - "time": "2025-08-14T20:33:29+00:00" + "time": "2026-01-03T14:43:18+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "5.2.2", + "version": "5.2.3", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "8937c397c8ae91b5af422ca8aa915c756062da74" + "reference": "8782f575032fedc36e2eb27c39c736054e2b6867" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8937c397c8ae91b5af422ca8aa915c756062da74", - "reference": "8937c397c8ae91b5af422ca8aa915c756062da74", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8782f575032fedc36e2eb27c39c736054e2b6867", + "reference": "8782f575032fedc36e2eb27c39c736054e2b6867", "shasum": "" }, "require": { @@ -15010,7 +15129,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.2" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.3" }, "funding": [ { @@ -15022,20 +15141,20 @@ "type": "patreon" } ], - "time": "2025-03-16T14:38:43+00:00" + "time": "2025-12-20T10:54:02+00:00" }, { "name": "web-token/jwt-library", - "version": "4.1.2", + "version": "4.1.3", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", - "reference": "621ff3ec618c6a34f63d47e467cefe8788871d6f" + "reference": "690d4dd47b78f423cb90457f858e4106e1deb728" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/621ff3ec618c6a34f63d47e467cefe8788871d6f", - "reference": "621ff3ec618c6a34f63d47e467cefe8788871d6f", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/690d4dd47b78f423cb90457f858e4106e1deb728", + "reference": "690d4dd47b78f423cb90457f858e4106e1deb728", "shasum": "" }, "require": { @@ -15099,7 +15218,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/4.1.2" + "source": "https://github.com/web-token/jwt-library/tree/4.1.3" }, "funding": [ { @@ -15111,27 +15230,27 @@ "type": "patreon" } ], - "time": "2025-11-17T21:14:49+00:00" + "time": "2025-12-18T14:27:35+00:00" }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", "shasum": "" }, "require": { "ext-ctype": "*", "ext-date": "*", "ext-filter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.2" }, "suggest": { "ext-intl": "", @@ -15141,7 +15260,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -15157,6 +15276,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -15167,9 +15290,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/2.1.2" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2026-01-13T14:02:24+00:00" } ], "packages-dev": [ @@ -15594,16 +15717,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.5.1", + "version": "12.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c467c59a4f6e04b942be422844e7a6352fa01b57" + "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c467c59a4f6e04b942be422844e7a6352fa01b57", - "reference": "c467c59a4f6e04b942be422844e7a6352fa01b57", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b", + "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b", "shasum": "" }, "require": { @@ -15618,7 +15741,7 @@ "sebastian/environment": "^8.0.3", "sebastian/lines-of-code": "^4.0", "sebastian/version": "^6.0", - "theseer/tokenizer": "^2.0" + "theseer/tokenizer": "^2.0.1" }, "require-dev": { "phpunit/phpunit": "^12.5.1" @@ -15659,7 +15782,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.1" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2" }, "funding": [ { @@ -15679,7 +15802,7 @@ "type": "tidelift" } ], - "time": "2025-12-08T07:17:58+00:00" + "time": "2025-12-24T07:03:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -15928,16 +16051,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.2", + "version": "12.5.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "06713c2633d6d832f2fe98a70511ecaa7cb92c1a" + "reference": "ba2d126905713bcf802c7f1e0d7507092ce9bf9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/06713c2633d6d832f2fe98a70511ecaa7cb92c1a", - "reference": "06713c2633d6d832f2fe98a70511ecaa7cb92c1a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ba2d126905713bcf802c7f1e0d7507092ce9bf9a", + "reference": "ba2d126905713bcf802c7f1e0d7507092ce9bf9a", "shasum": "" }, "require": { @@ -15951,7 +16074,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.1", + "phpunit/php-code-coverage": "^12.5.2", "phpunit/php-file-iterator": "^6.0.0", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", @@ -16005,7 +16128,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.2" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.5" }, "funding": [ { @@ -16029,20 +16152,20 @@ "type": "tidelift" } ], - "time": "2025-12-08T07:22:32+00:00" + "time": "2026-01-15T12:03:46+00:00" }, { "name": "rector/rector", - "version": "2.2.14", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "6d56bb0e94d4df4f57a78610550ac76ab403657d" + "reference": "9afc1bb43571b25629f353c61a9315b5ef31383a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/6d56bb0e94d4df4f57a78610550ac76ab403657d", - "reference": "6d56bb0e94d4df4f57a78610550ac76ab403657d", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/9afc1bb43571b25629f353c61a9315b5ef31383a", + "reference": "9afc1bb43571b25629f353c61a9315b5ef31383a", "shasum": "" }, "require": { @@ -16081,7 +16204,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.2.14" + "source": "https://github.com/rectorphp/rector/tree/2.3.1" }, "funding": [ { @@ -16089,7 +16212,7 @@ "type": "github" } ], - "time": "2025-12-09T10:57:55+00:00" + "time": "2026-01-13T15:13:58+00:00" }, { "name": "sebastian/cli-parser", @@ -17042,16 +17165,16 @@ }, { "name": "symfony/browser-kit", - "version": "v7.3.6", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "e9a9fd604296b17bf90939c3647069f1f16ef04e" + "reference": "9403aa24cacd754493c988e0bde22cadb509be5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/e9a9fd604296b17bf90939c3647069f1f16ef04e", - "reference": "e9a9fd604296b17bf90939c3647069f1f16ef04e", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/9403aa24cacd754493c988e0bde22cadb509be5e", + "reference": "9403aa24cacd754493c988e0bde22cadb509be5e", "shasum": "" }, "require": { @@ -17090,7 +17213,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v7.3.6" + "source": "https://github.com/symfony/browser-kit/tree/v7.3.9" }, "funding": [ { @@ -17110,7 +17233,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T07:57:47+00:00" + "time": "2025-12-14T08:06:00+00:00" }, { "name": "symfony/css-selector", @@ -17427,16 +17550,16 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v7.3.5", + "version": "v7.3.9", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "c2ed11cc0e9093fe0425ad52498d26a458842e0c" + "reference": "42c439f0d9a62b845700e037e8f4997cfa7119d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/c2ed11cc0e9093fe0425ad52498d26a458842e0c", - "reference": "c2ed11cc0e9093fe0425ad52498d26a458842e0c", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/42c439f0d9a62b845700e037e8f4997cfa7119d7", + "reference": "42c439f0d9a62b845700e037e8f4997cfa7119d7", "shasum": "" }, "require": { @@ -17492,7 +17615,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.5" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.9" }, "funding": [ { @@ -17512,7 +17635,7 @@ "type": "tidelift" } ], - "time": "2025-10-06T13:36:11+00:00" + "time": "2025-12-26T06:49:41+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/bundles.php b/config/bundles.php index 8781dbc..77def6c 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -18,4 +18,5 @@ return [ Presta\SitemapBundle\PrestaSitemapBundle::class => ['all' => true], Sentry\SentryBundle\SentryBundle::class => ['prod' => true], Vich\UploaderBundle\VichUploaderBundle::class => ['all' => 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..95e20b7 --- /dev/null +++ b/config/packages/knpu_oauth2_client.yaml @@ -0,0 +1,13 @@ +knpu_oauth2_client: + clients: + # This key 'keycloak' is what you'll use in your code + keycloak: + type: keycloak + # All these should be stored in your .env file + auth_server_url: '%env(KEYCLOAK_AUTH_SERVER_URL)%' + realm: '%env(KEYCLOAK_REALM)%' + client_id: '%env(KEYCLOAK_CLIENT_ID)%' + client_secret: '%env(KEYCLOAK_CLIENT_SECRET)%' + # The route name where Keycloak will redirect the user back to + redirect_route: connect_keycloak_check + redirect_params: {} diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 9db2dc1..98fd2e3 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -27,6 +27,7 @@ security: entry_point: App\Security\AuthenticationEntryPoint custom_authenticator: - App\Security\LoginFormAuthenticator + - App\Security\KeycloakAuthenticator logout: target: app_logout @@ -40,9 +41,8 @@ security: # algorithm: bcrypt role_hierarchy: - ROLE_ROOT: [ROLE_ADMIN] # ROLE_ROOT inclut ROLE_ADMIN, qui à son tour inclut ROLE_ARTEMIS + ROLE_ROOT: [ROLE_ADMIN] # access_control: - { path: ^/admin, roles: [ROLE_ADMIN] } - - { path: ^/console, roles: [ROLE_COSPLAY] } - { path: ^/, roles: PUBLIC_ACCESS } # Toutes les autres pages nécessitent une authentification complète diff --git a/docker-compose.yml b/docker-compose.yml index 3678339..ca5b374 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,65 +111,16 @@ services: # --- Service de Test d'Emails (MailHog) --- # Intercepte tous les emails envoyés en développement mailhog: - image: mailhog/mailhog:latest - container_name: crm_mailhog + image: axllent/mailpit:latest ports: - # Port 1025 pour le serveur SMTP factice - - "1025:1025" - # Port 8025 pour l'interface web de MailHog - - "8025:8025" + - "1025:1025" + - "8025:8025" networks: - - crm_network # Assignation au réseau commun - - # --- Service de Stockage Fichiers (MinIO) --- - # Fournit une API compatible S3 pour le stockage de fichiers - minio: - image: minio/minio:RELEASE.2025-02-03T21-03-04Z - container_name: crm_minio - ports: - # Port 9000 pour l'API S3 - - "9000:9000" - # Port 9001 pour la console web de MinIO - - "9001:9001" - environment: - MINIO_ROOT_USER: minio_user - MINIO_ROOT_PASSWORD: ChangeMeInProd! - volumes: - # Volume nommé pour la persistance des fichiers - - minio_data:/data - # Commande pour démarrer MinIO et lancer la console sur le bon port - command: server /data --console-address ":9001" - networks: - - crm_network # Assignation au réseau commun - - # --- Service de Gestion des Secrets (HashiCorp Vault) --- - vault: - image: hashicorp/vault:latest - container_name: crm_vault - ports: - - "8210:8200" # Mappe le port 8210 de l'hôte au port 8200 du conteneur Vault - - "8211:8201" # Mappe le port 8210 de l'hôte au port 8200 du conteneur Vault - - "8212:8202" # Mappe le port 8212 de l'hôte au port 8200 du conteneur Vault - volumes: - # Volume pour la persistance des données - - vault_data:/vault - # Volume pour monter notre fichier de configuration - environment: - VAULT_DEV_ROOT_TOKEN_ID: myroot - VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8201 - VAULT_LOCAL_CONFIG: '{"storage": {"file": {"path": "/vault/file"}}, "listener": [{"tcp": { "address": "0.0.0.0:8200", "tls_disable": true}}], "default_lease_ttl": "168h", "max_lease_ttl": "720h", "ui": true,"disable_mlock": false}' - # Lance Vault en mode serveur avec notre fichier de configuration - cap_add: - - IPC_LOCK - command: "server -dev" - networks: - - crm_network # Assignation au réseau commun + - crm_network # Assignation au réseau commun # Définition des volumes pour la persistance des données volumes: db_data: # Pour la base de données principale de Symfony - minio_data: # Pour le stockage de fichiers MinIO - vault_data: # Pour les données de HashiCorp Vault # Définition des réseaux networks: diff --git a/makefile b/makefile index 5129eb6..548c602 100644 --- a/makefile +++ b/makefile @@ -48,7 +48,8 @@ migrate: ## Applique les migrations composer-install: ## Installe les dépendances Composer @$(PHP_EXEC) composer install deps: composer-install ## Alias pour composer-install - +db_remove: ## Crée la base de données + @$(CONSOLE) doctrine:database:drop --force dbtest_add: ## Crée la base de données @$(CONSOLE) doctrine:database:create --env=test dbtest_migrate: ## Crée la base de données diff --git a/migrations/Version20251209163956.php b/migrations/Version20251211203538.php similarity index 98% rename from migrations/Version20251209163956.php rename to migrations/Version20251211203538.php index 1e99171..3415081 100644 --- a/migrations/Version20251209163956.php +++ b/migrations/Version20251211203538.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20251209163956 extends AbstractMigration +final class Version20251211203538 extends AbstractMigration { public function getDescription(): string { diff --git a/migrations/Version20260115165200.php b/migrations/Version20260115165200.php new file mode 100644 index 0000000..db2a964 --- /dev/null +++ b/migrations/Version20260115165200.php @@ -0,0 +1,44 @@ +addSql('ALTER TABLE account ADD keycloak_id VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE account ADD first_name VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE account ADD name VARCHAR(255) DEFAULT NULL'); + $this->addSql('DROP INDEX idx_75ea56e016ba31db'); + $this->addSql('DROP INDEX idx_75ea56e0e3bd61ce'); + $this->addSql('DROP INDEX idx_75ea56e0fb7336f0'); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); + } + + 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('ALTER TABLE "account" DROP keycloak_id'); + $this->addSql('ALTER TABLE "account" DROP first_name'); + $this->addSql('ALTER TABLE "account" DROP name'); + $this->addSql('DROP INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750'); + $this->addSql('CREATE INDEX idx_75ea56e016ba31db ON messenger_messages (delivered_at)'); + $this->addSql('CREATE INDEX idx_75ea56e0e3bd61ce ON messenger_messages (available_at)'); + $this->addSql('CREATE INDEX idx_75ea56e0fb7336f0 ON messenger_messages (queue_name)'); + } +} diff --git a/migrations/Version20260115165808.php b/migrations/Version20260115165808.php new file mode 100644 index 0000000..2bbc80e --- /dev/null +++ b/migrations/Version20260115165808.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE account ALTER password DROP NOT NULL'); + } + + 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('ALTER TABLE "account" ALTER password SET NOT NULL'); + } +} diff --git a/public/assets/images/logo.png b/public/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..30aad0a826f4bb75f2e6a3b40e406dd43309ae93 GIT binary patch literal 51402 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 z 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 OkmIlb000000002{8nk)< literal 0 HcmV?d00001 diff --git a/src/Command/AccountCommand.php b/src/Command/AccountCommand.php deleted file mode 100644 index a157dd9..0000000 --- a/src/Command/AccountCommand.php +++ /dev/null @@ -1,66 +0,0 @@ -title("Création d'un utilisateur administrateur"); - - - - $existingUser = $this->entityManager->getRepository(Account::class)->findOneBy(['email' => "jovann@siteconseil.fr"]); - if (!$existingUser instanceof Account) { - $password = TempPasswordGenerator::generate(); - $newUser = new Account(); - $newUser->setRoles(['ROLE_ROOT']); - $newUser->setUuid(Uuid::v4()); - $newUser->setIsActif(true); - $newUser->setIsFirstLogin(true); - $newUser->setEmail("jovann@siteconseil.fr"); - $newUser->setUsername("Jovann"); - - - - $hashedPassword = $this->userPasswordHasher->hashPassword($newUser, $password); - $newUser->setPassword($hashedPassword); - $this->eventDispatcher->dispatch(new CreatedAdminEvent($newUser,$password)); - - $this->entityManager->persist($newUser); - $this->entityManager->flush(); - - - $io->success("Utilisateur administrateur créé avec succès."); - } else { - $io->warning("Un utilisateur avec l'email existe déjà."); - } - - return Command::SUCCESS; - } -} diff --git a/src/Controller/Dashboard/HomeController.php b/src/Controller/Dashboard/HomeController.php new file mode 100644 index 0000000..0127c25 --- /dev/null +++ b/src/Controller/Dashboard/HomeController.php @@ -0,0 +1,40 @@ + false], methods: ['GET','POST'])] + public function crm(): Response + { + return $this->render('dashboard/home.twig'); + } + + + #[Route(path: '/crm/administrateur', name: 'app_crm_administrateur', options: ['sitemap' => false], methods: ['GET','POST'])] + public function administrateur(AccountRepository $accountRepository): Response + { + return $this->render('dashboard/administrateur.twig',[ + 'admins' => $accountRepository->findAll(), + ]); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index 9f9ec44..fd41b9b 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -8,6 +8,7 @@ use App\Form\RequestPasswordConfirmType; use App\Form\RequestPasswordRequestType; use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent; use App\Service\ResetPassword\Event\ResetPasswordEvent; +use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -21,9 +22,27 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; class HomeController extends AbstractController { - #[Route(path: '/', name: 'app_home', options: ['sitemap' => false], methods: ['GET'])] + + #[Route('/connect/keycloak', name: 'connect_keycloak_start')] + public function connect(ClientRegistry $clientRegistry) + { + // Redirects to Keycloak + return $clientRegistry + ->getClient('keycloak') + ->redirect(['email', 'profile','openid'], []); + } + + #[Route('/oauth/sso', name: 'connect_keycloak_check')] + public function connectCheck(Request $request) + { + // This method stays empty; the authenticator will intercept it! + } + #[Route(path: '/', name: 'app_home', options: ['sitemap' => false], methods: ['GET','POST'])] public function index(AuthenticationUtils $authenticationUtils): Response { + if($this->getUser()){ + return $this->redirectToRoute('app_crm'); + } return $this->render('home.twig',[ 'last_username' => $authenticationUtils->getLastUsername(), 'error' => $authenticationUtils->getLastAuthenticationError(), diff --git a/src/Entity/Account.php b/src/Entity/Account.php index 9c93382..46d3dd2 100644 --- a/src/Entity/Account.php +++ b/src/Entity/Account.php @@ -34,7 +34,7 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser #[ORM\Column] private array $roles = []; - #[ORM\Column] + #[ORM\Column(nullable: true)] private ?string $password = null; #[ORM\Column(length: 255)] @@ -57,6 +57,15 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser #[ORM\Column(nullable: true)] private ?bool $isActif = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $keycloakId = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $firstName = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $name = null; + public function __construct() { @@ -190,7 +199,6 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser $this->id, $this->email, $this->username, - $this->avatarFileName, )); } @@ -200,7 +208,6 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser $this->id, $this->email, $this->username, - $this->avatarFileName, ) = unserialize($data); } @@ -245,4 +252,40 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser return $this; } + + public function getKeycloakId(): ?string + { + return $this->keycloakId; + } + + public function setKeycloakId(?string $keycloakId): static + { + $this->keycloakId = $keycloakId; + + return $this; + } + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(?string $firstName): static + { + $this->firstName = $firstName; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): static + { + $this->name = $name; + + return $this; + } } diff --git a/src/Security/KeycloakAuthenticator.php b/src/Security/KeycloakAuthenticator.php new file mode 100644 index 0000000..4c75487 --- /dev/null +++ b/src/Security/KeycloakAuthenticator.php @@ -0,0 +1,101 @@ +clientRegistry = $clientRegistry; + $this->entityManager = $entityManager; + $this->router = $router; + } + + public function supports(Request $request): ?bool + { + // match the route name from the controller + return $request->attributes->get('_route') === 'connect_keycloak_check'; + } + + public function authenticate(Request $request): Passport + { + $client = $this->clientRegistry->getClient('keycloak'); + $accessToken = $this->fetchAccessToken($client); + + return new SelfValidatingPassport( + new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) { + /** @var \Stevenmaguire\OAuth2\Client\Provider\KeycloakResourceOwner $keycloakUser */ + $keycloakUser = $client->fetchUserFromToken($accessToken); + + + $email = $keycloakUser->getEmail(); + + $existingUser = $this->entityManager->getRepository(Account::class)->findOneBy(['keycloakId' => $keycloakUser->getId()]); + + if ($existingUser) { + return $existingUser; + } + + // 2) Optional: Find by email if ID doesn't match (syncing) + $user = $this->entityManager->getRepository(Account::class)->findOneBy(['email' => $email]); + + if (!$user) { + // 3) Create a new user if they don't exist + $user = new Account(); + $user->setUuid(Uuid::v4()); + $user->setRoles(['ROLE_ROOT']); + $user->setIsActif(true); + $user->setIsFirstLogin(false); + $user->setUsername($keycloakUser->getUsername()); + $user->setFirstName($keycloakUser->toArray()['given_name']); + $user->setName($keycloakUser->toArray()['family_name']); + $user->setEmail($email); + } + + $user->setKeycloakId($keycloakUser->getId()); + $this->entityManager->persist($user); + $this->entityManager->flush(); + + return $user; + }) + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + // Redirect to your homepage or dashboard after login + return new RedirectResponse($this->router->generate('app_home')); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $message = strtr($exception->getMessageKey(), $exception->getMessageData()); + return new Response($message, Response::HTTP_FORBIDDEN); + } + + public function start(Request $request, AuthenticationException $authException = null): Response + { + return new RedirectResponse($this->router->generate('connect_keycloak_start')); + } +} diff --git a/src/Service/Mailer/MailerSubscriber.php b/src/Service/Mailer/MailerSubscriber.php index d4df803..4172a4e 100644 --- a/src/Service/Mailer/MailerSubscriber.php +++ b/src/Service/Mailer/MailerSubscriber.php @@ -23,7 +23,7 @@ class MailerSubscriber $this->mailer->send( $account->getEmail(), $account->getUsername(), - "[CRM] - Création d'un compte administrateur", + "[LudikEvent] - Création d'un compte administrateur", "mails/new_admin.twig", [ 'username' => $account->getUsername(), diff --git a/symfony.lock b/symfony.lock index f79524d..5a428db 100644 --- a/symfony.lock +++ b/symfony.lock @@ -50,6 +50,18 @@ "knplabs/knp-paginator-bundle": { "version": "v6.10.0" }, + "knpuniversity/oauth2-client-bundle": { + "version": "2.20", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.20", + "ref": "1ff300d8c030f55c99219cc55050b97a695af3f6" + }, + "files": [ + "config/packages/knpu_oauth2_client.yaml" + ] + }, "league/flysystem-bundle": { "version": "3.6", "recipe": { diff --git a/templates/base.twig b/templates/base.twig index 2f18aa5..d36f37a 100644 --- a/templates/base.twig +++ b/templates/base.twig @@ -4,7 +4,7 @@ - {% block title %}Accueil{% endblock %} + Ludikevent | {% block title %}Accueil{% endblock %} diff --git a/templates/dashboard/administrateur.twig b/templates/dashboard/administrateur.twig new file mode 100644 index 0000000..385aba3 --- /dev/null +++ b/templates/dashboard/administrateur.twig @@ -0,0 +1,5 @@ +{% extends 'dashboard/base.twig' %} +{% block title %}Administrateur{% endblock %} +{% block body %} + {{ dump(admins) }} +{% endblock %} diff --git a/templates/dashboard/base.twig b/templates/dashboard/base.twig new file mode 100644 index 0000000..7e37fb5 --- /dev/null +++ b/templates/dashboard/base.twig @@ -0,0 +1,183 @@ + + + + + + Tableau de Bord Administratif + + {{ vite_asset('admin.js',{}) }} + + + + + +
+ + + + +
+ +
+ + + +
+ + +
+

+ {% block title %}{% endblock %} +

+
+ {% block actions %}{% endblock %} +
+
+ {% block body %}{% endblock %} + +
+
+ + + + + diff --git a/templates/dashboard/home.twig b/templates/dashboard/home.twig new file mode 100644 index 0000000..45fe5af --- /dev/null +++ b/templates/dashboard/home.twig @@ -0,0 +1,2 @@ +{% extends 'dashboard/base.twig' %} +{% block title %}Tableau de bord{% endblock %} diff --git a/templates/home.twig b/templates/home.twig index 72eb1bf..4980b84 100644 --- a/templates/home.twig +++ b/templates/home.twig @@ -4,6 +4,7 @@
+

{{ 'security.login'|trans }}

@@ -59,6 +60,12 @@ class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> {{ 'button.sign_in'|trans }} + + + {{ 'button.sso'|trans }} + +
diff --git a/templates/mails/base.twig b/templates/mails/base.twig index a2fd6fa..81299de 100644 --- a/templates/mails/base.twig +++ b/templates/mails/base.twig @@ -1,28 +1,56 @@ -{% extends 'mails/base.twig' %} +{# base.twig - Modèle d'e-mail MJML #} + + + {{ system.subject }} + + + + + + + .link-style { + color: #4A90E2; + text-decoration: none; + } + .footer-text { + font-size: 12px; + color: #888888; + } + + + + {# Section d'en-tête #} + + + {# Logo mis à jour pour SARL SITECONSEIL #} + + + -{% block content %} - Bonjour, + {# Section de contenu #} + + {# Titre dynamique ajouté avant le bloc de contenu, directement dans la section #} + {{ system.subject }} + + {% block content %} + {% endblock %} + + - {% if 'ROLE_CUSTOMER' in datas.account.roles %} - Nous avons reçu une demande de réinitialisation de mot de passe pour votre espace client. - {% else %} - Nous avons reçu une demande de réinitialisation de mot de passe pour votre compte E-Cosplay. - {% endif %} + {# Section d'espacement #} + + + + + - Pour réinitialiser votre mot de passe, veuillez cliquer sur le bouton ci-dessous. Ce lien est valable pour une durée limitée. - - - Réinitialiser mon mot de passe - - - - Ce lien expirera le {{ datas.request.expiresAt|date('d/m/Y à H:i') }}. -
- Veuillez l'utiliser avant cette date et heure. -
- - Si vous n'avez pas demandé cette réinitialisation de mot de passe, veuillez ignorer cet e-mail. Votre mot de passe actuel restera inchangé. - - Cordialement, - L'équipe E-Cosplay -{% endblock %} + {# Section de pied de page #} + + + + © {{ "now"|date("Y") }} LudikEvent. Tous droits réservés. + + + +
+
diff --git a/templates/mails/new_admin.twig b/templates/mails/new_admin.twig index bcb2545..10f09bd 100644 --- a/templates/mails/new_admin.twig +++ b/templates/mails/new_admin.twig @@ -25,6 +25,6 @@

Cordialement,
- L'équipe CRM + L'équipe LudikEvent {% endblock %} diff --git a/templates/security/forgot-password-confirm.twig b/templates/security/forgot-password-confirm.twig index 716ac89..9cc56f1 100644 --- a/templates/security/forgot-password-confirm.twig +++ b/templates/security/forgot-password-confirm.twig @@ -4,6 +4,7 @@ {% block body %}
+

{{ 'events.reset_password'|trans }} diff --git a/templates/security/forgot_password.twig b/templates/security/forgot_password.twig index df47775..464fd5d 100644 --- a/templates/security/forgot_password.twig +++ b/templates/security/forgot_password.twig @@ -5,6 +5,7 @@ {% block body %}
+

{{ 'events.forgot_password'|trans }} diff --git a/templates/security/forgot_password_success.twig b/templates/security/forgot_password_success.twig index 44b3da9..fc42409 100644 --- a/templates/security/forgot_password_success.twig +++ b/templates/security/forgot_password_success.twig @@ -5,6 +5,7 @@ {% block body %}
+

{{ 'events.reset_email_sent'|trans }} diff --git a/templates/txt-mails/base.twig b/templates/txt-mails/base.twig new file mode 100644 index 0000000..f817cc8 --- /dev/null +++ b/templates/txt-mails/base.twig @@ -0,0 +1,13 @@ +[LudikEvent] - {{ system.subject }} + +================================================== + +{% block content %} +{% endblock %} + +================================================== + +Si vous ne parvenez pas à cliquer sur un lien dans cet e-mail, veuillez copier et coller l'URL dans la barre d'adresse de votre navigateur. + +--- +© {{ "now"|date("Y") }} LudikEvent. Tous droits réservés. diff --git a/templates/txt-mails/new_admin.twig b/templates/txt-mails/new_admin.twig new file mode 100644 index 0000000..fc9047a --- /dev/null +++ b/templates/txt-mails/new_admin.twig @@ -0,0 +1,25 @@ +{% extends 'txt-mails/base.twig' %} + +{% block content %} + Bonjour, + + Nous avons le plaisir de vous informer que votre compte administrateur a été créé. + + Voici vos identifiants de connexion temporaires : + -------------------------------------------------- + Nom d'utilisateur : {{ datas.username }} + Mot de passe : {{ datas.password }} + -------------------------------------------------- + + Pour des raisons de sécurité, nous vous demandons de bien vouloir modifier votre mot de passe lors de votre première connexion. + + Vous pouvez vous connecter à votre compte en utilisant le lien ci-dessous : + + Lien de connexion : {{ system.path }}{{ datas.url }} + + Si vous avez des questions ou rencontrez des difficultés, n'hésitez pas à nous contacter. + + Cordialement, + + L'équipe LudikEvent +{% endblock %} diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 1e1f5fd..41c4ec5 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -28,3 +28,4 @@ logged_in_as: Connecté en tant que logout_link: Déconnexion page.login: Connexion logged_admin: Administration +button.sso: Connexion SSO diff --git a/update.sh b/update.sh index 4a7ea5e..d7101d3 100644 --- a/update.sh +++ b/update.sh @@ -5,9 +5,9 @@ GREEN='\033[0;32m' CYAN='\033[0;36m' RESET='\033[0m' # Reset color to default -echo "${CYAN}#######################${RESET}" -echo "${CYAN}# E-PAGE UPDATE START #${RESET}" -echo "${CYAN}#######################${RESET}" +echo "${CYAN}####################################${RESET}" +echo "${CYAN}# LUDIKEVENT INTRANET UPDATE START #${RESET}" +echo "${CYAN}####################################${RESET}" ansible-playbook -i ansible/hosts.ini ansible/playbook.yml echo "${CYAN}##############${RESET}" echo "${CYAN}# END UPDATE #${RESET}"