From 633f0a54658f0a572de707ce72b340eb08bfeef0 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Mon, 10 Jul 2023 17:09:32 +0200 Subject: [PATCH 01/57] Override upstream Readme in .github --- .github/README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/README.md diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 000000000..76c7b0ac8 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,56 @@ +# user_oidc: Customisation of OpenID app for MagentaCLOUD + +The app extends the standard `user_oidc` Nextcloud app, +see [upstream configuration hints for basic setup](https://github.com/nextcloud/user_oidc/README.md) + +The app is extended by the following features + +## Event-based provisioning (upstream contribution candidate) +The mechanism allows to implement custom puser provisioning logic in a separate Nextcloud app by +registering and handling a attribute change and provisioning event: + +``` +use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\Bootstrap\IBootstrap; +use OCP\AppFramework\Bootstrap\IRegistrationContext; + +class Application extends App implements IBootstrap { +... + public function register(IRegistrationContext $context): void { + $context->registerEventListener(AttributeMappedEvent::class, MyUserAttributeListener::class); + $context->registerEventListener(UserAccountChangeEvent::class, MyUserAccountChangeListener::class); + } +... +} +``` +The provisioning handler should return a `OCA\UserOIDC\Event\UserAccountChangeResult` object + +## Telekom-specific bearer token + +Due to historic reason, Telekom bearer tokens have a close to standard structure, but +require special security implementation in detail. The customisation overrides te standard + + +### Requiring web-token libraries +The central configuration branch `nmc/2372-central-setup` automatic merge will frequently fail if composer +upstream + +The fast and easy way to bring it back to sync with upstream is: +``` +git checkout nmc/2372-central-setup +git rebase --onto main nmc/2372-central-setup +# manually take over everything from upstream for composer.lock (TODO: automate that) + +# update web-token dependencies in composer.lock +composer update web-token +``` +It is recommended to leave the version management for all other libraries to upstream +and only update web-token with the dedicated `composer update web-token`. + + +### Configuring an additional Bearer preshared secret with provider +TODO + +### Testing Bearer secrets +TODO \ No newline at end of file From a30a3d72cc77374c75ac2d0691c63d7c1043c3aa Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Mon, 10 Jul 2023 17:18:04 +0200 Subject: [PATCH 02/57] Correct some wordings --- .github/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/README.md b/.github/README.md index 76c7b0ac8..cc522ede0 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,9 +1,11 @@ -# user_oidc: Customisation of OpenID app for MagentaCLOUD +# MagentaCLOUD user_oidc + +Customisation of the Nextcloud delivered OpenID connect app for MagentaCLOUD. The app extends the standard `user_oidc` Nextcloud app, -see [upstream configuration hints for basic setup](https://github.com/nextcloud/user_oidc/README.md) +see [upstream configuration hints for basic setup](https://github.com/nextcloud/user_oidc/blob/main/README.md) -The app is extended by the following features +The app is extended by the following features: ## Event-based provisioning (upstream contribution candidate) The mechanism allows to implement custom puser provisioning logic in a separate Nextcloud app by From 50f0cf4c3e182437b324462f0a0f6d77bb9a616f Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Tue, 11 Jul 2023 18:06:01 +0200 Subject: [PATCH 03/57] Add automatic phpunit run after assembly --- .github/workflows/nmc-custom-phpunit.yml | 39 ++++++++++++++++++++ .github/workflows/nmc-custom-versions.yml | 43 +++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 .github/workflows/nmc-custom-phpunit.yml create mode 100644 .github/workflows/nmc-custom-versions.yml diff --git a/.github/workflows/nmc-custom-phpunit.yml b/.github/workflows/nmc-custom-phpunit.yml new file mode 100644 index 000000000..ab230b4b1 --- /dev/null +++ b/.github/workflows/nmc-custom-phpunit.yml @@ -0,0 +1,39 @@ +### +# SPDX-License-Identifier: AGPL-3.0 +# +# Author: Bernd rederlechner +# +# Assemble a customisation for trunk (no backports) and stable +# (backport xor trunk) +# +# It creates review (user-specific) customisations branches +# - customisation-- +# - customisation-- + +name: MCLOUD phpunit (customisation change) + +### +# The automated unittets cycles are started as soon as a new +# customisation branch is pushed +on: + push: + branches: + - customisation-*master + - customisation-*nmcstable/** + +jobs: + build-custom: + strategy: + fail-fast: false + matrix: + phpversion: ['8.0', '8.1'] + database: ['mysql'] + custombase: [ "main", "nmcstable/25.0.6" ] + uses: nextmcloud/.github/.github/workflows/nmc-custom-app-phpunit.yml@master + with: + assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} + appname: 'user_oidc' + server-branch: ${{ matrix.custombase }} + phpversion: ${{ matrix.phpversion }} + database: ${{ matrix.database }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/nmc-custom-versions.yml b/.github/workflows/nmc-custom-versions.yml new file mode 100644 index 000000000..635791314 --- /dev/null +++ b/.github/workflows/nmc-custom-versions.yml @@ -0,0 +1,43 @@ +### +# SPDX-License-Identifier: AGPL-3.0 +# +# Author: Bernd rederlechner +# +# Assemble a customisation for trunk (no backports) and stable +# (backport xor trunk) +# +# It creates review (user-specific) customisations branches +# - customisation-- +# - customisation-- + +name: MCLOUD custom app versions + +### +# The customisation-* branches are always reassembled if a customisation branch +# is updated or included into a custom PR +on: + workflow_dispatch: + pull_request: + types: + - opened + - reopened + - synchronize + branches: + - master + - main + - trunk + - nmcstable/** + # - stable/** + +jobs: + build-custom: + strategy: + fail-fast: false + matrix: + custombase: [ "main", "nmcstable/25.0.6" ] + uses: nextmcloud/.github/.github/workflows/nmc-custom-assembly.yml@master + with: + trunk: "main" + stable: ${{ matrix.custombase }} + result: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} + secrets: inherit From 83d64613771eed52f6b4cd45e52958f19894698d Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Tue, 11 Jul 2023 18:19:30 +0200 Subject: [PATCH 04/57] Correct trunk name for customisation branch --- .github/workflows/nmc-custom-phpunit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nmc-custom-phpunit.yml b/.github/workflows/nmc-custom-phpunit.yml index ab230b4b1..76e047406 100644 --- a/.github/workflows/nmc-custom-phpunit.yml +++ b/.github/workflows/nmc-custom-phpunit.yml @@ -18,8 +18,8 @@ name: MCLOUD phpunit (customisation change) on: push: branches: - - customisation-*master - - customisation-*nmcstable/** + - customisation-*-main + - customisation-*-nmcstable/** jobs: build-custom: From c9727c73f84d06d557b7404f47d517d73f1856bb Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Tue, 11 Jul 2023 18:37:51 +0200 Subject: [PATCH 05/57] Debug scheduling on push --- .github/workflows/nmc-custom-phpunit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nmc-custom-phpunit.yml b/.github/workflows/nmc-custom-phpunit.yml index 76e047406..1d5426f38 100644 --- a/.github/workflows/nmc-custom-phpunit.yml +++ b/.github/workflows/nmc-custom-phpunit.yml @@ -18,8 +18,8 @@ name: MCLOUD phpunit (customisation change) on: push: branches: - - customisation-*-main - - customisation-*-nmcstable/** + - 'customisation*-main' + - 'customisation*-nmcstable/25.0.6' jobs: build-custom: From 9775679726929c7081d2ad1a9919b021b8cc0703 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 12 Jul 2023 08:50:24 +0200 Subject: [PATCH 06/57] Include phpunit in versions assembling --- .github/workflows/nmc-custom-phpunit.yml | 39 ----------------------- .github/workflows/nmc-custom-versions.yml | 20 +++++++++++- 2 files changed, 19 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/nmc-custom-phpunit.yml diff --git a/.github/workflows/nmc-custom-phpunit.yml b/.github/workflows/nmc-custom-phpunit.yml deleted file mode 100644 index 1d5426f38..000000000 --- a/.github/workflows/nmc-custom-phpunit.yml +++ /dev/null @@ -1,39 +0,0 @@ -### -# SPDX-License-Identifier: AGPL-3.0 -# -# Author: Bernd rederlechner -# -# Assemble a customisation for trunk (no backports) and stable -# (backport xor trunk) -# -# It creates review (user-specific) customisations branches -# - customisation-- -# - customisation-- - -name: MCLOUD phpunit (customisation change) - -### -# The automated unittets cycles are started as soon as a new -# customisation branch is pushed -on: - push: - branches: - - 'customisation*-main' - - 'customisation*-nmcstable/25.0.6' - -jobs: - build-custom: - strategy: - fail-fast: false - matrix: - phpversion: ['8.0', '8.1'] - database: ['mysql'] - custombase: [ "main", "nmcstable/25.0.6" ] - uses: nextmcloud/.github/.github/workflows/nmc-custom-app-phpunit.yml@master - with: - assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} - appname: 'user_oidc' - server-branch: ${{ matrix.custombase }} - phpversion: ${{ matrix.phpversion }} - database: ${{ matrix.database }} - secrets: inherit \ No newline at end of file diff --git a/.github/workflows/nmc-custom-versions.yml b/.github/workflows/nmc-custom-versions.yml index 635791314..6b0c3eb60 100644 --- a/.github/workflows/nmc-custom-versions.yml +++ b/.github/workflows/nmc-custom-versions.yml @@ -30,7 +30,8 @@ on: # - stable/** jobs: - build-custom: + + assemble: strategy: fail-fast: false matrix: @@ -41,3 +42,20 @@ jobs: stable: ${{ matrix.custombase }} result: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} secrets: inherit + + phpunit: + strategy: + fail-fast: false + matrix: + phpversion: ['8.0', '8.1'] + database: ['mysql'] + custombase: [ "main", "nmcstable/25.0.6" ] + uses: nextmcloud/.github/.github/workflows/nmc-custom-app-phpunit.yml@master + need: assemble + with: + assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} + appname: 'user_oidc' + server-branch: ${{ matrix.custombase }} + phpversion: ${{ matrix.phpversion }} + database: ${{ matrix.database }} + secrets: inherit \ No newline at end of file From 38e09f364d4146925eb5f097caedeb9fb8b706cd Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 12 Jul 2023 09:08:29 +0200 Subject: [PATCH 07/57] Fix syntax --- .github/workflows/nmc-custom-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-versions.yml b/.github/workflows/nmc-custom-versions.yml index 6b0c3eb60..ff9767903 100644 --- a/.github/workflows/nmc-custom-versions.yml +++ b/.github/workflows/nmc-custom-versions.yml @@ -57,5 +57,5 @@ jobs: appname: 'user_oidc' server-branch: ${{ matrix.custombase }} phpversion: ${{ matrix.phpversion }} - database: ${{ matrix.database }} + database: ${{ matrix.database }} secrets: inherit \ No newline at end of file From ce03abbc0c54c10abb45896c5e999c4438b23e3b Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 12 Jul 2023 09:16:58 +0200 Subject: [PATCH 08/57] Fix needs syntax --- .github/workflows/nmc-custom-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-versions.yml b/.github/workflows/nmc-custom-versions.yml index ff9767903..7fa8a8320 100644 --- a/.github/workflows/nmc-custom-versions.yml +++ b/.github/workflows/nmc-custom-versions.yml @@ -51,7 +51,7 @@ jobs: database: ['mysql'] custombase: [ "main", "nmcstable/25.0.6" ] uses: nextmcloud/.github/.github/workflows/nmc-custom-app-phpunit.yml@master - need: assemble + needs: assemble with: assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} appname: 'user_oidc' From 85bf2f9a13b92e359db67f91616b5cc8fc28d749 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 12 Jul 2023 12:32:57 +0200 Subject: [PATCH 09/57] Move readme to central setup --- .github/README.md | 58 ----------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 .github/README.md diff --git a/.github/README.md b/.github/README.md deleted file mode 100644 index cc522ede0..000000000 --- a/.github/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# MagentaCLOUD user_oidc - -Customisation of the Nextcloud delivered OpenID connect app for MagentaCLOUD. - -The app extends the standard `user_oidc` Nextcloud app, -see [upstream configuration hints for basic setup](https://github.com/nextcloud/user_oidc/blob/main/README.md) - -The app is extended by the following features: - -## Event-based provisioning (upstream contribution candidate) -The mechanism allows to implement custom puser provisioning logic in a separate Nextcloud app by -registering and handling a attribute change and provisioning event: - -``` -use OCP\AppFramework\App; -use OCP\AppFramework\Bootstrap\IBootContext; -use OCP\AppFramework\Bootstrap\IBootstrap; -use OCP\AppFramework\Bootstrap\IRegistrationContext; - -class Application extends App implements IBootstrap { -... - public function register(IRegistrationContext $context): void { - $context->registerEventListener(AttributeMappedEvent::class, MyUserAttributeListener::class); - $context->registerEventListener(UserAccountChangeEvent::class, MyUserAccountChangeListener::class); - } -... -} -``` -The provisioning handler should return a `OCA\UserOIDC\Event\UserAccountChangeResult` object - -## Telekom-specific bearer token - -Due to historic reason, Telekom bearer tokens have a close to standard structure, but -require special security implementation in detail. The customisation overrides te standard - - -### Requiring web-token libraries -The central configuration branch `nmc/2372-central-setup` automatic merge will frequently fail if composer -upstream - -The fast and easy way to bring it back to sync with upstream is: -``` -git checkout nmc/2372-central-setup -git rebase --onto main nmc/2372-central-setup -# manually take over everything from upstream for composer.lock (TODO: automate that) - -# update web-token dependencies in composer.lock -composer update web-token -``` -It is recommended to leave the version management for all other libraries to upstream -and only update web-token with the dedicated `composer update web-token`. - - -### Configuring an additional Bearer preshared secret with provider -TODO - -### Testing Bearer secrets -TODO \ No newline at end of file From 8d822a44443334741ae395ea031295f15cd36d1b Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Sat, 19 Aug 2023 08:54:14 +0200 Subject: [PATCH 10/57] Refactor for working fast-fail precheck --- .github/workflows/nmc-custom-release.yml | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/nmc-custom-release.yml diff --git a/.github/workflows/nmc-custom-release.yml b/.github/workflows/nmc-custom-release.yml new file mode 100644 index 000000000..5962a538f --- /dev/null +++ b/.github/workflows/nmc-custom-release.yml @@ -0,0 +1,58 @@ +### +# SPDX-License-Identifier: AGPL-3.0 +# +# Author: Bernd rederlechner +# +# Builds a stable release package based on a release assembly +# customisation-- +# +# As soon as a package is deployed to production, the tag and the branch +# MUST STAY FOR 2 years and not deleted. +# +# Release packages, tags and customisation branches not delivered to production should +# be deleted asap a newer release is available. +# + +name: MCLOUD custom app release + +on: + workflow_dispatch: + inputs: + increment: + description: 'Release increment' + required: true + type: number + branch: + type: choice + description: Branch to build a package from + options: + - main + - stable25 + - stable26 + - stable27 + default: main + +jobs: + check-custom: + uses: nextmcloud/.github/.github/workflows/nmc-app-precond.yml@master + with: + versionbranch: ${{ inputs.branch }} + increment: ${{ inputs.increment }} + secrets: inherit + assemble-custom: + uses: nextmcloud/.github/.github/workflows/nmc-custom-assembly.yml@master + needs: check-custom + with: + trunk: 'main' + stable: ${{ inputs.branch }} + result: ${{ format('customisation-{0}-{1}', inputs.branch, inputs.increment ) }} + secrets: inherit + build-custom: + uses: nextmcloud/.github/.github/workflows/nmc-custom-app-build.yml@master + needs: [ check-custom, assemble-custom ] + with: + appname: ${{ needs.check-custom.outputs.appname }} + assembly: ${{ format('customisation-{0}-{1}', inputs.branch , inputs.increment ) }} + tag: ${{ needs.check-custom.outputs.tag }} + prerelease: ${{ inputs.branch == 'main' && true || false }} + secrets: inherit From 9a3c0370fb1884703e9ca257a78f0fdcf36db6eb Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Fri, 1 Sep 2023 12:37:19 +0200 Subject: [PATCH 11/57] Remove obsolete stable versions base in matrix --- .github/workflows/nmc-custom-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-versions.yml b/.github/workflows/nmc-custom-versions.yml index 7fa8a8320..6d262e273 100644 --- a/.github/workflows/nmc-custom-versions.yml +++ b/.github/workflows/nmc-custom-versions.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - custombase: [ "main", "nmcstable/25.0.6" ] + custombase: [ "main" ] uses: nextmcloud/.github/.github/workflows/nmc-custom-assembly.yml@master with: trunk: "main" From cb09967fcddec713653f9b86283b6a2379954055 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Fri, 1 Sep 2023 19:19:41 +0200 Subject: [PATCH 12/57] Add required composer dependencies programmatically --- ...release.yml => nmc-custom-app-release.yml} | 14 +++- ...rsions.yml => nmc-custom-app-versions.yml} | 15 +++- .../workflows/nmc-custom-oidc-composer.yml | 82 +++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) rename .github/workflows/{nmc-custom-release.yml => nmc-custom-app-release.yml} (83%) rename .github/workflows/{nmc-custom-versions.yml => nmc-custom-app-versions.yml} (82%) create mode 100644 .github/workflows/nmc-custom-oidc-composer.yml diff --git a/.github/workflows/nmc-custom-release.yml b/.github/workflows/nmc-custom-app-release.yml similarity index 83% rename from .github/workflows/nmc-custom-release.yml rename to .github/workflows/nmc-custom-app-release.yml index 5962a538f..b34abf189 100644 --- a/.github/workflows/nmc-custom-release.yml +++ b/.github/workflows/nmc-custom-app-release.yml @@ -47,9 +47,21 @@ jobs: stable: ${{ inputs.branch }} result: ${{ format('customisation-{0}-{1}', inputs.branch, inputs.increment ) }} secrets: inherit + + composerdep: + strategy: + fail-fast: false + matrix: + custombase: [ "main" ] + uses: ./.github/workflows/nmc-custom-oidc-composer.yml + needs: assemble-custom + with: + assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} + secrets: inherit + build-custom: uses: nextmcloud/.github/.github/workflows/nmc-custom-app-build.yml@master - needs: [ check-custom, assemble-custom ] + needs: [ check-custom, composerdep ] with: appname: ${{ needs.check-custom.outputs.appname }} assembly: ${{ format('customisation-{0}-{1}', inputs.branch , inputs.increment ) }} diff --git a/.github/workflows/nmc-custom-versions.yml b/.github/workflows/nmc-custom-app-versions.yml similarity index 82% rename from .github/workflows/nmc-custom-versions.yml rename to .github/workflows/nmc-custom-app-versions.yml index 6d262e273..e18f19412 100644 --- a/.github/workflows/nmc-custom-versions.yml +++ b/.github/workflows/nmc-custom-app-versions.yml @@ -43,15 +43,26 @@ jobs: result: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} secrets: inherit + composerdep: + strategy: + fail-fast: false + matrix: + custombase: [ "main" ] + uses: ./.github/workflows/nmc-custom-oidc-composer.yml + needs: assemble + with: + assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} + secrets: inherit + phpunit: strategy: fail-fast: false matrix: phpversion: ['8.0', '8.1'] database: ['mysql'] - custombase: [ "main", "nmcstable/25.0.6" ] + custombase: [ "main" ] uses: nextmcloud/.github/.github/workflows/nmc-custom-app-phpunit.yml@master - needs: assemble + needs: composerdep with: assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} appname: 'user_oidc' diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml new file mode 100644 index 000000000..474b32575 --- /dev/null +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -0,0 +1,82 @@ +### +# SPDX-License-Identifier: AGPL-3.0 +# +# Author: Bernd Rederlechner Date: Fri, 1 Sep 2023 19:33:57 +0200 Subject: [PATCH 13/57] Remove commit push blocker --- .github/workflows/nmc-custom-oidc-composer.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index 474b32575..ca247fbdf 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -45,7 +45,6 @@ jobs: # set user in case commits are needed git config user.name $BUILD_USER git config user.email $BUILD_EMAIL - git remote set-url origin http://no.such.host # install php dependencies - name: Set up php ${{ env.PHP_VERSION }} From aa83c5bcdd7e9e75a6f87446759d43c189f9c9b3 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Mon, 4 Sep 2023 08:49:39 +0200 Subject: [PATCH 14/57] Fix assembly branch name for dependency check --- .github/workflows/nmc-custom-app-release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/nmc-custom-app-release.yml b/.github/workflows/nmc-custom-app-release.yml index b34abf189..64d287cef 100644 --- a/.github/workflows/nmc-custom-app-release.yml +++ b/.github/workflows/nmc-custom-app-release.yml @@ -51,12 +51,10 @@ jobs: composerdep: strategy: fail-fast: false - matrix: - custombase: [ "main" ] uses: ./.github/workflows/nmc-custom-oidc-composer.yml needs: assemble-custom with: - assembly: ${{ format('customisation-{0}-{1}', github.actor, matrix.custombase) }} + assembly: ${{ format('customisation-{0}-{1}', inputs.branch, inputs.increment) }} secrets: inherit build-custom: From d4702f6e29c333f14b94542796a32d7e99815809 Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Wed, 29 Oct 2025 07:57:31 +0100 Subject: [PATCH 15/57] Update nmc-custom-oidc-composer.yml --- .github/workflows/nmc-custom-oidc-composer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index ca247fbdf..d4f2c5273 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -26,7 +26,7 @@ jobs: BUILD_USER: ${{ github.actor }} BUILD_EMAIL: ${{ github.actor }}@users.noreply.github.com BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} - PHP_VERSION: ${{ vars.PHP_VERSION || '8.1' }} + PHP_VERSION: ${{ vars.PHP_VERSION || '8.2' }} steps: - name: Fetch custom assembly id: checkout_custom From 01e7a4b0898bf4608d01a84529fb0f8df3d7c5e4 Mon Sep 17 00:00:00 2001 From: memurats Date: Mon, 4 May 2026 15:32:33 +0200 Subject: [PATCH 16/57] update --- .../workflows/nmc-custom-oidc-composer.yml | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index d4f2c5273..d014d43ae 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -1,7 +1,7 @@ ### # SPDX-License-Identifier: AGPL-3.0 # -# Author: Bernd Rederlechner # # user_oidc is (so far) the only app where we add php packages # to Nextcloud standard. We add these commandline based in build @@ -10,7 +10,6 @@ name: MCLOUD custom user_oidc dependencies - on: workflow_call: inputs: @@ -23,16 +22,15 @@ jobs: build-custom: runs-on: ubuntu-latest env: - BUILD_USER: ${{ github.actor }} - BUILD_EMAIL: ${{ github.actor }}@users.noreply.github.com - BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} - PHP_VERSION: ${{ vars.PHP_VERSION || '8.2' }} + BUILD_USER: ${{ github.actor }} + BUILD_EMAIL: ${{ github.actor }}@users.noreply.github.com + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} + PHP_VERSION: ${{ vars.PHP_VERSION || '8.2' }} + steps: - name: Fetch custom assembly - id: checkout_custom + id: checkout_custom uses: actions/checkout@v3 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: repository: ${{ github.repository }} ref: ${{ inputs.assembly }} @@ -40,29 +38,28 @@ jobs: token: ${{ env.BUILD_TOKEN }} - name: Prepare GIT modifications - id: prepare_git + id: prepare_git run: | - # set user in case commits are needed - git config user.name $BUILD_USER - git config user.email $BUILD_EMAIL + git config user.name "$BUILD_USER" + git config user.email "$BUILD_EMAIL" - # install php dependencies - name: Set up php ${{ env.PHP_VERSION }} uses: shivammathur/setup-php@v2 with: php-version: ${{ env.PHP_VERSION }} coverage: none - + - name: Check composer.json id: check_composer uses: andstor/file-existence-action@v1 with: files: "./composer.json" - + - name: Install composer JWT dependencies if: steps.check_composer.outputs.files_exists == 'true' run: | - composer require web-token/jwt-core:^2.0 \ + composer require \ + web-token/jwt-core:^2.2 \ web-token/jwt-encryption:^2.2 \ web-token/jwt-signature:^2.2 \ web-token/jwt-encryption-algorithm-aescbc:^2.2 \ @@ -71,11 +68,23 @@ jobs: web-token/jwt-encryption-algorithm-pbes2:^2.2 \ web-token/jwt-signature-algorithm-hmac:^2.2 \ web-token/jwt-signature-algorithm-rsa:^2.2 \ - web-token/jwt-util-ecc:^2.2 + web-token/jwt-util-ecc:^2.2 \ + spomky-labs/aes-key-wrap:^6 \ + --with-all-dependencies - - name: Commit push composer.json/.lock '${{ env.CUSTOM_BRANCH }}' + vendor/bin/mozart compose + composer dump-autoload + + - name: Commit and push dependency changes + if: steps.check_composer.outputs.files_exists == 'true' id: pushcomposerdep run: | - git commit -m "Add jwt-token composer library dependencies" composer.json composer.lock - git push origin $CUSTOM_BRANCH - + git add composer.json composer.lock lib/Vendor lib/autoload + + if git diff --cached --quiet; then + echo "No composer dependency changes to commit." + exit 0 + fi + + git commit -m "Add JWT composer library dependencies" + git push origin HEAD:${{ inputs.assembly }} \ No newline at end of file From 2fc69237360dab400b6e2f15c2fbb3538e2ab5c0 Mon Sep 17 00:00:00 2001 From: memurats Date: Mon, 4 May 2026 16:00:48 +0200 Subject: [PATCH 17/57] fix --- .github/workflows/nmc-custom-oidc-composer.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index d014d43ae..47af37df2 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -78,8 +78,13 @@ jobs: - name: Commit and push dependency changes if: steps.check_composer.outputs.files_exists == 'true' id: pushcomposerdep + - name: Commit and push dependency changes run: | - git add composer.json composer.lock lib/Vendor lib/autoload + git add composer.json composer.lock lib/Vendor + + if [ -d lib/autoload ]; then + git add lib/autoload + fi if git diff --cached --quiet; then echo "No composer dependency changes to commit." From cc7be83f1e23a3ea1687467793fef1aea5ac51a1 Mon Sep 17 00:00:00 2001 From: memurats Date: Mon, 4 May 2026 16:09:02 +0200 Subject: [PATCH 18/57] fix --- .github/workflows/nmc-custom-oidc-composer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index 47af37df2..c818f8c4f 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -92,4 +92,4 @@ jobs: fi git commit -m "Add JWT composer library dependencies" - git push origin HEAD:${{ inputs.assembly }} \ No newline at end of file + git push origin HEAD:${{ inputs.assembly }} From e52056ff0bd65188f4626a1c7b65c2705258e882 Mon Sep 17 00:00:00 2001 From: memurats Date: Mon, 4 May 2026 16:13:20 +0200 Subject: [PATCH 19/57] fix --- .github/workflows/nmc-custom-oidc-composer.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index c818f8c4f..a10e28dea 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -22,7 +22,7 @@ jobs: build-custom: runs-on: ubuntu-latest env: - BUILD_USER: ${{ github.actor }} + BUILD_USER: ${{ github.actor }} BUILD_EMAIL: ${{ github.actor }}@users.noreply.github.com BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} PHP_VERSION: ${{ vars.PHP_VERSION || '8.2' }} @@ -78,7 +78,8 @@ jobs: - name: Commit and push dependency changes if: steps.check_composer.outputs.files_exists == 'true' id: pushcomposerdep - - name: Commit and push dependency changes + env: + ASSEMBLY_BRANCH: ${{ inputs.assembly }} run: | git add composer.json composer.lock lib/Vendor @@ -92,4 +93,4 @@ jobs: fi git commit -m "Add JWT composer library dependencies" - git push origin HEAD:${{ inputs.assembly }} + git push origin HEAD:$ASSEMBLY_BRANCH From eb190b26245c2c370ea366d07849d8f3bea517b6 Mon Sep 17 00:00:00 2001 From: memurats Date: Mon, 4 May 2026 16:20:24 +0200 Subject: [PATCH 20/57] fix --- .github/workflows/nmc-custom-oidc-composer.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index a10e28dea..5b412d722 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -81,10 +81,11 @@ jobs: env: ASSEMBLY_BRANCH: ${{ inputs.assembly }} run: | - git add composer.json composer.lock lib/Vendor + git add composer.json composer.lock + git add -f lib/Vendor if [ -d lib/autoload ]; then - git add lib/autoload + git add -f lib/autoload fi if git diff --cached --quiet; then From 9ea87ae3e9b1c661aea4841ca3ff37bb991e37b0 Mon Sep 17 00:00:00 2001 From: memurats Date: Tue, 5 May 2026 15:24:39 +0200 Subject: [PATCH 21/57] return status ok --- lib/Controller/LoginController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php index abba810cc..6d9ed3008 100644 --- a/lib/Controller/LoginController.php +++ b/lib/Controller/LoginController.php @@ -1070,7 +1070,7 @@ private function getBackchannelLogoutErrorResponse( 'error' => $error, 'error_description' => $description, ], - Http::STATUS_BAD_REQUEST, + Http::STATUS_OK, ); // Tell the Idp not to cache the response // Per RFC : https://openid.net/specs/openid-connect-backchannel-1_0.html#BCResponse From 7c41c239e4bf2641756e9ee754992316e49aa525 Mon Sep 17 00:00:00 2001 From: memurats Date: Tue, 5 May 2026 15:27:06 +0200 Subject: [PATCH 22/57] added check and redirect --- lib/Controller/LoginController.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php index abba810cc..cb3c37c3b 100644 --- a/lib/Controller/LoginController.php +++ b/lib/Controller/LoginController.php @@ -370,6 +370,11 @@ public function code(string $state = '', string $code = '', string $scope = '', $this->logger->debug('Code login with core: ' . $code . ' and state: ' . $state); if ($error !== '') { + if (!$this->isMobileDevice()) { + $cancelRedirectUrl = $this->config->getSystemValue('user_oidc.cancel_redirect_url', 'https://cloud.telekom-dienste.de/'); + return new RedirectResponse($cancelRedirectUrl); + } + $this->logger->warning('Code login error', ['error' => $error, 'error_description' => $error_description]); if ($this->isDebugModeEnabled()) { return new JSONResponse([ @@ -1078,6 +1083,22 @@ private function getBackchannelLogoutErrorResponse( return $response; } + private function isMobileDevice(): bool { + $mobileKeywords = $this->config->getSystemValue('user_oidc.mobile_keywords', ['Android', 'iPhone', 'iPad', 'iPod', 'Windows Phone', 'Mobile', 'webOS', 'BlackBerry', 'Opera Mini', 'IEMobile']); + + if (!isset($_SERVER['HTTP_USER_AGENT'])) { + return false; // if no user-agent is set, assume desktop + } + + foreach ($mobileKeywords as $keyword) { + if (stripos($_SERVER['HTTP_USER_AGENT'], $keyword) !== false) { + return true; // device is mobile + } + } + + return false; // device is desktop + } + private function toCodeChallenge(string $data): string { // Basically one big work around for the base64url decode being weird $h = pack('H*', hash('sha256', $data)); From a1288f5b0e61e48ed06e3bce6e0b2c68d58101b8 Mon Sep 17 00:00:00 2001 From: memurats Date: Tue, 5 May 2026 15:34:05 +0200 Subject: [PATCH 23/57] backchannel logout fix --- lib/Controller/LoginController.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php index abba810cc..2cfbcbbfb 100644 --- a/lib/Controller/LoginController.php +++ b/lib/Controller/LoginController.php @@ -702,17 +702,6 @@ public function code(string $state = '', string $code = '', string $scope = '', $this->eventDispatcher->dispatchTyped(new UserLoggedInEvent($user, $userId, null, false)); } - $storeLoginTokenEnabled = $this->appConfig->getValueString(Application::APP_ID, 'store_login_token', '0', lazy: true) === '1'; - if ($storeLoginTokenEnabled) { - // store all token information for potential token exchange requests - $tokenData = array_merge( - $data, - ['provider_id' => $providerId], - ); - $this->tokenService->storeToken($tokenData); - } - $this->config->setUserValue($user->getUID(), Application::APP_ID, 'had_token_once', '1'); - // Set last password confirm to the future as we don't have passwords to confirm against with SSO $this->session->set('last-password-confirm', $this->timeFactory->getTime() + 4 * 365 * 24 * 3600); @@ -720,7 +709,7 @@ public function code(string $state = '', string $code = '', string $scope = '', try { $authToken = $this->authTokenProvider->getToken($this->session->getId()); $this->sessionMapper->createOrUpdateSession( - $idTokenPayload->sid ?? 'fallback-sid', + $idTokenPayload->{'urn:telekom.com:session_token'} ?? 'fallback-sid', $idTokenPayload->sub ?? 'fallback-sub', $idTokenPayload->iss ?? 'fallback-iss', $authToken->getId(), @@ -1099,4 +1088,15 @@ private function cleanupSessionState(string $sessionKeySuffix): void { $this->session->remove(self::CODE_VERIFIER . $sessionKeySuffix); $this->session->remove(self::TIMESTAMP . $sessionKeySuffix); } + + /** + * Backward compatible function for MagentaCLOUD to smoothly transition to new config + * + * @PublicPage + * @NoCSRFRequired + * @BruteForceProtection(action: 'userOidcBackchannelLogout') + */ + public function telekomBackChannelLogout(string $logout_token = ''): JSONResponse { + return $this->backChannelLogout('Telekom', $logout_token); + } } From d5ad6850f627a36b41ee6df218a30410eac1848b Mon Sep 17 00:00:00 2001 From: memurats Date: Tue, 5 May 2026 16:10:34 +0200 Subject: [PATCH 24/57] added bearer token secret --- lib/Command/UpsertProvider.php | 17 +- lib/Controller/SettingsController.php | 14 +- lib/Db/Provider.php | 5 + lib/Db/ProviderMapper.php | 7 +- .../Version00008Date20211114183344.php | 26 ++ .../Version010304Date20230902125945.php | 76 ++++ src/components/SettingsForm.vue | 9 + .../BearerSettingsTest.php\342\200\216" | 393 ++++++++++++++++++ 8 files changed, 542 insertions(+), 5 deletions(-) create mode 100644 lib/Migration/Version00008Date20211114183344.php create mode 100644 lib/Migration/Version010304Date20230902125945.php create mode 100644 "tests/unit/MagentaCloud/BearerSettingsTest.php\342\200\216" diff --git a/lib/Command/UpsertProvider.php b/lib/Command/UpsertProvider.php index f24575676..30f30ed0b 100644 --- a/lib/Command/UpsertProvider.php +++ b/lib/Command/UpsertProvider.php @@ -185,6 +185,7 @@ protected function configure(): void { ->addOption('clientsecret-file', null, InputOption::VALUE_REQUIRED, 'File that contains the OpenID client secret') ->addOption('clientsecret-env', null, InputOption::VALUE_REQUIRED, 'Environment variable that contains the OpenID client secret') ->addOption('discoveryuri', 'd', InputOption::VALUE_REQUIRED, 'OpenID discovery endpoint uri') + ->addOption('bearersecret', 'bs', InputOption::VALUE_OPTIONAL, 'Telekom bearer token requires a different client secret for bearer tokens') ->addOption('endsessionendpointuri', 'e', InputOption::VALUE_REQUIRED, 'OpenID end session endpoint uri') ->addOption('postlogouturi', 'p', InputOption::VALUE_REQUIRED, 'Post logout URI') ->addOption('scope', 'o', InputOption::VALUE_OPTIONAL, 'OpenID requested value scopes, if not set defaults to "openid email profile"'); @@ -217,10 +218,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->listProviders($input, $output); } + // bearersecret is usually base64 encoded, + // but SAM delivers it non-encoded by default + // so always encode/decode for this field + $bearersecret = $input->getOption('bearersecret'); + if ($bearersecret !== null) { + $bearersecret = $this->crypto->encrypt($this->base64UrlEncode($bearersecret)); + } + // check if any option for updating is provided $updateOptions = array_filter($input->getOptions(), static function ($value, $option) { return in_array($option, [ - 'identifier', 'clientid', 'clientsecret', 'discoveryuri', 'endsessionendpointuri', 'postlogouturi', 'scope', + 'identifier', 'clientid', 'clientsecret', 'discoveryuri', 'endsessionendpointuri', 'postlogouturi', 'scope', 'bearersecret', ...array_keys(self::EXTRA_OPTIONS), ]) && $value !== null; }, ARRAY_FILTER_USE_BOTH); @@ -261,7 +270,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } try { $provider = $this->providerMapper->createOrUpdateProvider( - $identifier, $clientId, $clientSecret, $discoveryuri, $scope, $endsessionendpointuri, $postLogoutUri + $identifier, $clientId, $clientSecret, $discoveryuri, $scope, $endsessionendpointuri, $postLogoutUri, $bearersecret ); // invalidate JWKS cache (even if it was just created) $this->providerService->setSetting($provider->getId(), ProviderService::SETTING_JWKS_CACHE, ''); @@ -283,6 +292,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + private function base64UrlEncode(string $data): string { + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); + } + private function listProviders(InputInterface $input, OutputInterface $output): int { $outputFormat = $input->getOption('output') ?? 'table'; $providers = $this->providerMapper->getProviders(); diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index ce50f3b62..1aa28a79c 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -101,7 +101,7 @@ private function isDiscoveryEndpointValid($url) { */ #[PasswordConfirmationRequired] #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT, tags: ['user_oidc_settings'])] - public function createProvider(string $identifier, string $clientId, string $clientSecret, string $discoveryEndpoint, + public function createProvider(string $identifier, string $clientId, string $clientSecret, string $discoveryEndpoint, string $bearerSecret, array $settings = [], string $scope = 'openid email profile', ?string $endSessionEndpoint = null, ?string $postLogoutUri = null): DataResponse { if ($this->providerService->getProviderByIdentifier($identifier) !== null) { @@ -126,6 +126,8 @@ public function createProvider(string $identifier, string $clientId, string $cli $provider->setEndSessionEndpoint($endSessionEndpoint ?: null); $provider->setPostLogoutUri($postLogoutUri ?: null); $provider->setScope($scope); + $encryptedBearerSecret = $this->crypto->encrypt($this->base64UrlEncode($bearerSecret)); + $provider->setBearerSecret($encryptedBearerSecret); $provider = $this->providerMapper->insert($provider); $providerSettings = $this->providerService->setSettings($provider->getId(), $settings); @@ -153,7 +155,7 @@ public function createProvider(string $identifier, string $clientId, string $cli */ #[PasswordConfirmationRequired] #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT, tags: ['user_oidc_settings'])] - public function updateProvider(int $providerId, string $identifier, string $clientId, string $discoveryEndpoint, ?string $clientSecret = null, + public function updateProvider(int $providerId, string $identifier, string $clientId, string $discoveryEndpoint, ?string $clientSecret = null, ?string $bearerSecret = null, array $settings = [], string $scope = 'openid email profile', ?string $endSessionEndpoint = null, ?string $postLogoutUri = null): DataResponse { $provider = $this->providerMapper->getProvider($providerId); @@ -177,6 +179,10 @@ public function updateProvider(int $providerId, string $identifier, string $clie $encryptedClientSecret = $this->crypto->encrypt($clientSecret); $provider->setClientSecret($encryptedClientSecret); } + if ($bearerSecret) { + $encryptedBearerSecret = $this->crypto->encrypt($this->base64UrlEncode($bearerSecret)); + $provider->setBearerSecret($encryptedBearerSecret); + } $provider->setDiscoveryEndpoint($discoveryEndpoint); $provider->setEndSessionEndpoint($endSessionEndpoint ?: null); $provider->setPostLogoutUri($postLogoutUri ?: null); @@ -191,6 +197,10 @@ public function updateProvider(int $providerId, string $identifier, string $clie return new DataResponse(array_merge($provider->jsonSerialize(), ['settings' => $providerSettings])); } + private function base64UrlEncode(string $data): string { + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); + } + /** * Delete a provider * diff --git a/lib/Db/Provider.php b/lib/Db/Provider.php index a08cd4ebe..23deaad50 100644 --- a/lib/Db/Provider.php +++ b/lib/Db/Provider.php @@ -23,6 +23,9 @@ * @method \void setEndSessionEndpoint(?string $endSessionEndpoint) * @method \string|\null getPostLogoutUri() * @method \void setPostLogoutUri(?string $postLogoutUri) + * @method \string|\null getBearerSecret() + * @method \void setBearerSecret(string $bearerSecret) + * @method \string|\null getScope() * @method \void setScope(string $scope) */ class Provider extends Entity implements \JsonSerializable { @@ -40,6 +43,8 @@ class Provider extends Entity implements \JsonSerializable { /** @var string */ protected $postLogoutUri; /** @var string */ + protected $bearerSecret; + /** @var string */ protected $scope; /** diff --git a/lib/Db/ProviderMapper.php b/lib/Db/ProviderMapper.php index ceb780885..1d02e60df 100644 --- a/lib/Db/ProviderMapper.php +++ b/lib/Db/ProviderMapper.php @@ -75,13 +75,14 @@ public function getProviders(): array { * @throws MultipleObjectsReturnedException */ public function createOrUpdateProvider( - string $identifier, + ?string $identifier = null, ?string $clientId = null, ?string $clientSecret = null, ?string $discoveryUri = null, string $scope = 'openid email profile', ?string $endSessionEndpointUri = null, ?string $postLogoutUri = null, + ?string $bearersecret = null, ): Provider { try { $provider = $this->findProviderByIdentifier($identifier); @@ -102,6 +103,9 @@ public function createOrUpdateProvider( if ($postLogoutUri !== null) { $provider->setPostLogoutUri($postLogoutUri); } + if ($bearersecret !== null) { + $provider->setBearerSecret($bearersecret); + } $provider->setScope($scope); return $this->update($provider); @@ -118,6 +122,7 @@ public function createOrUpdateProvider( $provider->setDiscoveryEndpoint($discoveryUri); $provider->setEndSessionEndpoint($endSessionEndpointUri); $provider->setPostLogoutUri($postLogoutUri); + $provider->setBearerSecret($bearersecret ?? ''); $provider->setScope($scope); return $this->insert($provider); diff --git a/lib/Migration/Version00008Date20211114183344.php b/lib/Migration/Version00008Date20211114183344.php new file mode 100644 index 000000000..fb787817c --- /dev/null +++ b/lib/Migration/Version00008Date20211114183344.php @@ -0,0 +1,26 @@ +getTable('user_oidc_providers'); + $table->addColumn('bearer_secret', 'string', [ + 'notnull' => true, + 'length' => 64, + 'default' => '', + ]); + + return $schema; + } +} diff --git a/lib/Migration/Version010304Date20230902125945.php b/lib/Migration/Version010304Date20230902125945.php new file mode 100644 index 000000000..4a49b88d8 --- /dev/null +++ b/lib/Migration/Version010304Date20230902125945.php @@ -0,0 +1,76 @@ +connection = $connection; + $this->crypto = $crypto; + } + + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $tableName = 'user_oidc_providers'; + + if ($schema->hasTable($tableName)) { + $table = $schema->getTable($tableName); + if ($table->hasColumn('bearer_secret')) { + $column = $table->getColumn('bearer_secret'); + $column->setLength(512); + return $schema; + } + } + + return null; + } + + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + $tableName = 'user_oidc_providers'; + + // update secrets in user_oidc_providers and user_oidc_id4me + $qbUpdate = $this->connection->getQueryBuilder(); + $qbUpdate->update($tableName) + ->set('bearer_secret', $qbUpdate->createParameter('updateSecret')) + ->where( + $qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId')) + ); + + $qbSelect = $this->connection->getQueryBuilder(); + $qbSelect->select('id', 'bearer_secret') + ->from($tableName); + $req = $qbSelect->executeQuery(); + while ($row = $req->fetch()) { + $id = $row['id']; + $secret = $row['bearer_secret']; + $encryptedSecret = $this->crypto->encrypt($secret); + $qbUpdate->setParameter('updateSecret', $encryptedSecret, IQueryBuilder::PARAM_STR); + $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT); + $qbUpdate->executeStatement(); + } + $req->closeCursor(); + } +} diff --git a/src/components/SettingsForm.vue b/src/components/SettingsForm.vue index 8abdab669..c6a70ed6a 100644 --- a/src/components/SettingsForm.vue +++ b/src/components/SettingsForm.vue @@ -32,6 +32,15 @@ :required="!update" autocomplete="off">

+

+ + +

{{ t('user_oidc', 'Warning, if the protocol of the URLs in the discovery content is HTTP, the ID token will be delivered through an insecure connection.') }} diff --git "a/tests/unit/MagentaCloud/BearerSettingsTest.php\342\200\216" "b/tests/unit/MagentaCloud/BearerSettingsTest.php\342\200\216" new file mode 100644 index 000000000..bee3d735f --- /dev/null +++ "b/tests/unit/MagentaCloud/BearerSettingsTest.php\342\200\216" @@ -0,0 +1,393 @@ +requestMock = $this->createMock(IRequest::class); + + $this->config = $this->createMock(IConfig::class); + $this->providerMapper = $this->createMock(ProviderMapper::class); + $providers = [ + new \OCA\UserOIDC\Db\Provider(), + ]; + $providers[0]->setId(1); + $providers[0]->setIdentifier('Fraesbook'); + + $this->providerMapper->expects(self::any()) + ->method('getProviders') + ->willReturn($providers); + + $this->providerService = $this->getMockBuilder(ProviderService::class) + ->setConstructorArgs([ $this->config, $this->providerMapper]) + ->onlyMethods(['getProviderByIdentifier']) + ->getMock(); + $this->crypto = $app->getContainer()->get(ICrypto::class); + } + + protected function mockCreateUpdate( + string $providername, + ?string $clientid, + ?string $clientsecret, + ?string $discovery, + string $scope, + ?string $bearersecret, + array $options, + int $id = 2, + ) { + $provider = $this->getMockBuilder(Provider::class) + ->addMethods(['getIdentifier', 'getId']) + ->getMock(); + $provider->expects($this->any()) + ->method('getIdentifier') + ->willReturn($providername); + $provider->expects($this->any()) + ->method('getId') + ->willReturn($id); + + $this->providerMapper->expects($this->once()) + ->method('createOrUpdateProvider') + ->with( + $this->equalTo($providername), + $this->equalTo($clientid), + $this->anything(), + $this->equalTo($discovery), + $this->equalTo($scope), + $this->anything() + ) + ->willReturnCallback(function ($id, $clientid, $secret, $discovery, $scope, $bsecret) use ($clientsecret, $bearersecret, $provider) { + if ($secret !== null) { + $this->assertEquals($clientsecret, $this->crypto->decrypt($secret)); + } else { + $this->assertNull($secret); + } + if ($bsecret !== null) { + $this->assertEquals($bearersecret, \Base64Url\Base64Url::decode($this->crypto->decrypt($bsecret))); + } else { + $this->assertNull($bsecret); + } + return $provider; + }); + + + $this->config->expects($this->any()) + ->method('setAppValue') + ->with($this->equalTo(Application::APP_ID), $this->anything(), $this->anything()) + ->willReturnCallback(function ($appid, $key, $value) use ($options) { + if (array_key_exists($key, $options)) { + $this->assertEquals($options[$key], $value); + } + return ''; + }); + } + + + public function testCommandAddProvider() { + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn(null); + + $this->mockCreateUpdate('Telekom', + '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', + 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', + 'bearersecret***', + [ + 'provider-2-' . ProviderService::SETTING_UNIQUE_UID => '0', + 'provider-2-' . ProviderService::SETTING_MAPPING_DISPLAYNAME => 'urn:telekom.com:displayname', + 'provider-2-' . ProviderService::SETTING_MAPPING_EMAIL => 'urn:telekom.com:mainEmail', + 'provider-2-' . ProviderService::SETTING_MAPPING_QUOTA => 'quota', + 'provider-2-' . ProviderService::SETTING_MAPPING_UID => 'sub' + ]); + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--clientid' => '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', + '--clientsecret' => 'clientsecret***', + '--bearersecret' => 'bearersecret***', + '--discoveryuri' => 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + '--scope' => 'openid email profile', + '--unique-uid' => '0', + '--mapping-display-name' => 'urn:telekom.com:displayname', + '--mapping-email' => 'urn:telekom.com:mainEmail', + '--mapping-quota' => 'quota', + '--mapping-uid' => 'sub', + ]); + + + //$output = $commandTester->getOutput(); + //$this->assertContains('done', $output); + } + + protected function mockProvider(string $providername, + string $clientid, + string $clientsecret, + string $discovery, + string $scope, + string $bearersecret, + int $id = 2) : Provider { + $provider = $this->getMockBuilder(Provider::class) + ->addMethods(['getIdentifier', 'getClientId', 'getClientSecret', 'getBearerSecret', 'getDiscoveryEndpoint']) + ->setMethods(['getScope', 'getId']) + ->getMock(); + $provider->expects($this->any()) + ->method('getIdentifier') + ->willReturn($providername); + $provider->expects($this->any()) + ->method('getId') + ->willReturn(2); + $provider->expects($this->any()) + ->method('getClientId') + ->willReturn($clientid); + $provider->expects($this->any()) + ->method('getClientSecret') + ->willReturn($clientsecret); + $provider->expects($this->any()) + ->method('getBearerSecret') + ->willReturn(\Base64Url\Base64Url::encode($bearersecret)); + $provider->expects($this->any()) + ->method('getDiscoveryEndpoint') + ->willReturn($discovery); + $provider->expects($this->any()) + ->method('getScope') + ->willReturn($scope); + + return $provider; + } + + public function testCommandUpdateFull() { + $provider = $this->getMockBuilder(Provider::class) + ->addMethods(['getIdentifier', 'getClientId', 'getClientSecret', 'getBearerSecret', 'getDiscoveryEndpoint']) + ->setMethods(['getScope']) + ->getMock(); + $provider->expects($this->any()) + ->method('getIdentifier') + ->willReturn('Telekom'); + $provider->expects($this->never())->method('getClientId'); + $provider->expects($this->never())->method('getClientSecret'); + $provider->expects($this->never())->method('getBearerSecret'); + $provider->expects($this->never())->method('getDiscoveryEndpoint'); + $provider->expects($this->never())->method('getScope'); + + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn(null); + $this->mockCreateUpdate('Telekom', + '10TVL0SAM30000004902NEXTMAGENTACLOUDTEST', + 'client*secret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-unknown/openid-configuration', + 'openid profile', + 'bearer*secret***', + [ + 'provider-2-' . ProviderService::SETTING_UNIQUE_UID => '1', + 'provider-2-' . ProviderService::SETTING_MAPPING_DISPLAYNAME => 'urn:telekom.com:displaykrame', + 'provider-2-' . ProviderService::SETTING_MAPPING_EMAIL => 'urn:telekom.com:mainDemail', + 'provider-2-' . ProviderService::SETTING_MAPPING_QUOTA => 'quotas', + 'provider-2-' . ProviderService::SETTING_MAPPING_UID => 'flop' + ]); + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + $commandTester->execute([ + 'identifier' => 'Telekom', + '--clientid' => '10TVL0SAM30000004902NEXTMAGENTACLOUDTEST', + '--clientsecret' => 'client*secret***', + '--bearersecret' => 'bearer*secret***', + '--discoveryuri' => 'https://accounts.login00.idm.ver.sul.t-online.de/.well-unknown/openid-configuration', + '--scope' => 'openid profile', + '--mapping-display-name' => 'urn:telekom.com:displaykrame', + '--mapping-email' => 'urn:telekom.com:mainDemail', + '--mapping-quota' => 'quotas', + '--mapping-uid' => 'flop', + '--unique-uid' => '1' + ]); + } + + public function testCommandUpdateSingleClientId() { + $provider = $this->mockProvider('Telekom', '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', 'bearersecret***'); + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn($provider); + $this->mockCreateUpdate( + 'Telekom', + '10TVL0SAM30000004903NEXTMAGENTACLOUDTEST', + null, + null, + 'openid email profile', + null, + []); + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--clientid' => '10TVL0SAM30000004903NEXTMAGENTACLOUDTEST', + ]); + } + + + public function testCommandUpdateSingleClientSecret() { + $provider = $this->mockProvider('Telekom', '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', 'bearersecret***'); + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn($provider); + $this->mockCreateUpdate( + 'Telekom', + null, + '***clientsecret***', + null, + 'openid email profile', + null, + []); + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--clientsecret' => '***clientsecret***', + ]); + } + + public function testCommandUpdateSingleBearerSecret() { + $provider = $this->mockProvider('Telekom', '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', 'bearersecret***'); + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn($provider); + $this->mockCreateUpdate( + 'Telekom', + null, + null, + null, + 'openid email profile', + '***bearersecret***', + []); + + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--bearersecret' => '***bearersecret***', + ]); + } + + public function testCommandUpdateSingleDiscoveryEndpoint() { + $provider = $this->mockProvider('Telekom', '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', 'bearersecret***'); + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn($provider); + $this->mockCreateUpdate( + 'Telekom', + null, + null, + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-unknown/openid-configuration', + 'openid email profile', + null, []); + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--discoveryuri' => 'https://accounts.login00.idm.ver.sul.t-online.de/.well-unknown/openid-configuration', + ]); + } + + public function testCommandUpdateSingleScope() { + $provider = $this->mockProvider('Telekom', '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', 'bearersecret***'); + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn($provider); + $this->mockCreateUpdate( + 'Telekom', + null, + null, + null, + 'openid profile', + '***bearersecret***', + []); + + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--scope' => 'openid profile', + ]); + } + + public function testCommandUpdateSingleUniqueUid() { + $provider = $this->mockProvider('Telekom', '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST', 'clientsecret***', + 'https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration', + 'openid email profile', 'bearersecret***'); + $this->providerService->expects($this->once()) + ->method('getProviderByIdentifier') + ->with($this->equalTo('Telekom')) + ->willReturn($provider); + $this->mockCreateUpdate( + 'Telekom', + null, + null, + null, + 'openid email profile', + null, + ['provider-2-' . ProviderService::SETTING_UNIQUE_UID => '1']); + + $command = new UpsertProvider($this->providerService, $this->providerMapper, $this->crypto); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'identifier' => 'Telekom', + '--unique-uid' => '1', + ]); + } +} \ No newline at end of file From 1a7d81fa7a3401c14f602e6bd4890fa3397f0cb8 Mon Sep 17 00:00:00 2001 From: memurats Date: Tue, 5 May 2026 16:16:09 +0200 Subject: [PATCH 25/57] fix coding style --- lib/Command/UpsertProvider.php | 2 +- lib/Migration/Version00008Date20211114183344.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Command/UpsertProvider.php b/lib/Command/UpsertProvider.php index 30f30ed0b..1d44cdc3e 100644 --- a/lib/Command/UpsertProvider.php +++ b/lib/Command/UpsertProvider.php @@ -218,7 +218,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->listProviders($input, $output); } - // bearersecret is usually base64 encoded, + // bearersecret is usually base64 encoded, // but SAM delivers it non-encoded by default // so always encode/decode for this field $bearersecret = $input->getOption('bearersecret'); diff --git a/lib/Migration/Version00008Date20211114183344.php b/lib/Migration/Version00008Date20211114183344.php index fb787817c..ba2cb904e 100644 --- a/lib/Migration/Version00008Date20211114183344.php +++ b/lib/Migration/Version00008Date20211114183344.php @@ -15,11 +15,11 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $schema = $schemaClosure(); $table = $schema->getTable('user_oidc_providers'); - $table->addColumn('bearer_secret', 'string', [ - 'notnull' => true, - 'length' => 64, - 'default' => '', - ]); + $table->addColumn('bearer_secret', 'string', [ + 'notnull' => true, + 'length' => 64, + 'default' => '', + ]); return $schema; } From 4373758e2586be43c86c9c68969b11ec66b4d41d Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Wed, 6 May 2026 11:52:12 +0200 Subject: [PATCH 26/57] Refactor GitHub Actions workflow for user_oidc --- .../workflows/nmc-custom-oidc-composer.yml | 59 +++---------------- 1 file changed, 9 insertions(+), 50 deletions(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index 5b412d722..afe95fcaa 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -3,10 +3,8 @@ # # Author: Bernd Rederlechner # -# user_oidc is (so far) the only app where we add php packages -# to Nextcloud standard. We add these commandline based in build -# to avoid continuous merge conflicts due to "composer.lock" -# merge problems +# user_oidc brings its PHP dependencies via composer.json. +# composer install also runs Mozart via post-install-cmd. name: MCLOUD custom user_oidc dependencies @@ -22,14 +20,11 @@ jobs: build-custom: runs-on: ubuntu-latest env: - BUILD_USER: ${{ github.actor }} - BUILD_EMAIL: ${{ github.actor }}@users.noreply.github.com BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} - PHP_VERSION: ${{ vars.PHP_VERSION || '8.2' }} + PHP_VERSION: ${{ vars.PHP_VERSION || '8.1' }} steps: - name: Fetch custom assembly - id: checkout_custom uses: actions/checkout@v3 with: repository: ${{ github.repository }} @@ -37,17 +32,12 @@ jobs: fetch-depth: 0 token: ${{ env.BUILD_TOKEN }} - - name: Prepare GIT modifications - id: prepare_git - run: | - git config user.name "$BUILD_USER" - git config user.email "$BUILD_EMAIL" - - - name: Set up php ${{ env.PHP_VERSION }} + - name: Set up PHP ${{ env.PHP_VERSION }} uses: shivammathur/setup-php@v2 with: php-version: ${{ env.PHP_VERSION }} coverage: none + tools: composer - name: Check composer.json id: check_composer @@ -55,43 +45,12 @@ jobs: with: files: "./composer.json" - - name: Install composer JWT dependencies + - name: Install composer dependencies and build prefixed vendor if: steps.check_composer.outputs.files_exists == 'true' run: | - composer require \ - web-token/jwt-core:^2.2 \ - web-token/jwt-encryption:^2.2 \ - web-token/jwt-signature:^2.2 \ - web-token/jwt-encryption-algorithm-aescbc:^2.2 \ - web-token/jwt-encryption-algorithm-ecdh-es:^2.2 \ - web-token/jwt-encryption-algorithm-rsa:^2.2 \ - web-token/jwt-encryption-algorithm-pbes2:^2.2 \ - web-token/jwt-signature-algorithm-hmac:^2.2 \ - web-token/jwt-signature-algorithm-rsa:^2.2 \ - web-token/jwt-util-ecc:^2.2 \ - spomky-labs/aes-key-wrap:^6 \ - --with-all-dependencies - - vendor/bin/mozart compose - composer dump-autoload + composer install --no-interaction --prefer-dist - - name: Commit and push dependency changes + - name: Run unit tests if: steps.check_composer.outputs.files_exists == 'true' - id: pushcomposerdep - env: - ASSEMBLY_BRANCH: ${{ inputs.assembly }} run: | - git add composer.json composer.lock - git add -f lib/Vendor - - if [ -d lib/autoload ]; then - git add -f lib/autoload - fi - - if git diff --cached --quiet; then - echo "No composer dependency changes to commit." - exit 0 - fi - - git commit -m "Add JWT composer library dependencies" - git push origin HEAD:$ASSEMBLY_BRANCH + XDEBUG_MODE=off composer test:unit From 31c3fd1e3ea0424c1422a9fa2b92552689826368 Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Wed, 6 May 2026 11:54:56 +0200 Subject: [PATCH 27/57] Update PHP version to 8.3 in workflow --- .github/workflows/nmc-custom-oidc-composer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index afe95fcaa..8066b60d6 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest env: BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} - PHP_VERSION: ${{ vars.PHP_VERSION || '8.1' }} + PHP_VERSION: ${{ vars.PHP_VERSION || '8.3' }} steps: - name: Fetch custom assembly From 2e004b9d5c95048f9c5f09f6e4e17235e31c1edc Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 12:23:54 +0200 Subject: [PATCH 28/57] added token service --- lib/MagentaBearer/InvalidTokenException.php | 8 + lib/MagentaBearer/SignatureException.php | 6 + lib/MagentaBearer/TokenService.php | 185 ++++++++++++++ tests/bootstrap.php | 22 +- .../MagentaCloud/BearerTokenServiceTest.php | 72 ++++++ .../unit/MagentaCloud/BearerTokenTestCase.php | 202 +++++++++++++++ .../MagentaCloud/HeaderBearerTokenTest.php | 234 ++++++++++++++++++ .../unit/MagentaCloud/SamBearerTokenTest.php | 61 +++++ 8 files changed, 789 insertions(+), 1 deletion(-) create mode 100644 lib/MagentaBearer/InvalidTokenException.php create mode 100644 lib/MagentaBearer/SignatureException.php create mode 100644 lib/MagentaBearer/TokenService.php create mode 100644 tests/unit/MagentaCloud/BearerTokenServiceTest.php create mode 100644 tests/unit/MagentaCloud/BearerTokenTestCase.php create mode 100644 tests/unit/MagentaCloud/HeaderBearerTokenTest.php create mode 100644 tests/unit/MagentaCloud/SamBearerTokenTest.php diff --git a/lib/MagentaBearer/InvalidTokenException.php b/lib/MagentaBearer/InvalidTokenException.php new file mode 100644 index 000000000..af97581b7 --- /dev/null +++ b/lib/MagentaBearer/InvalidTokenException.php @@ -0,0 +1,8 @@ +jweDecrypter = new JWEDecrypter( + $keyEncryptionAlgorithmManager, + $contentEncryptionAlgorithmManager, + $compressionMethodManager, + ); + + $this->encryptionSerializerManager = new JWESerializerManager([ + new JWECompactSerializer(), + ]); + + $this->jwsVerifier = new JWSVerifier($signatureAlgorithmManager); + + $this->serializerManager = new JWSSerializerManager([ + new JWSCompactSerializer(), + ]); + } + + public function decryptToken(string $rawToken, string $decryptKey): JWS { + $numSegments = substr_count($rawToken, '.') + 1; + $this->logger->debug('Bearer access token received', [ + 'segments' => $numSegments, + ]); + + $key = new JWK([ + 'kty' => 'oct', + 'k' => $decryptKey, + ]); + + if ($numSegments > 3) { + try { + $jwe = $this->encryptionSerializerManager->unserialize($rawToken); + } catch (\InvalidArgumentException $e) { + throw new InvalidTokenException('Invalid encrypted bearer token', 0, $e); + } + + if (!$this->jweDecrypter->decryptUsingKey($jwe, $key, 0)) { + throw new InvalidTokenException('Unknown bearer encryption format'); + } + + $payload = $jwe->getPayload(); + if ($payload === null || $payload === '') { + throw new InvalidTokenException('Empty decrypted bearer token payload'); + } + + return $this->serializerManager->unserialize($payload); + } + + try { + return $this->serializerManager->unserialize($rawToken); + } catch (\InvalidArgumentException $e) { + throw new InvalidTokenException('Invalid bearer token', 0, $e); + } + } + + public function decode(JWS $decodedToken): object { + $payload = $decodedToken->getPayload(); + if ($payload === null || $payload === '') { + throw new InvalidTokenException('Empty bearer token payload'); + } + + $samContent = json_decode($payload, false); + if (!is_object($samContent)) { + throw new InvalidTokenException('Invalid bearer token JSON payload'); + } + + $attributeName = 'urn:telekom.com:idm:at:attributes'; + if (isset($samContent->{$attributeName}) && is_iterable($samContent->{$attributeName})) { + foreach ($samContent->{$attributeName} as $claimKeyValue) { + if (isset($claimKeyValue->name, $claimKeyValue->value)) { + $samContent->{'urn:telekom.com:' . $claimKeyValue->name} = $claimKeyValue->value; + } + } + + unset($samContent->{$attributeName}); + } + + $this->logger->debug('Adapted OpenID-like Telekom SAM3 access token'); + + return $samContent; + } + + public function verifySignature(JWS $decodedToken, string $signKey): void { + $key = new JWK([ + 'kty' => 'oct', + 'k' => $signKey, + ]); + + if (!$this->jwsVerifier->verifyWithKey($decodedToken, $key, 0)) { + throw new SignatureException('Invalid signature'); + } + } + + public function verifyClaims(object $claims, array $audiences = [], int $leeway = 60): void { + $timestamp = $this->timeFactory->getTime(); + + if (isset($claims->nbf) && is_numeric($claims->nbf) && (int)$claims->nbf > ($timestamp + $leeway)) { + throw new InvalidTokenException( + 'Cannot handle token prior to ' . date(\DateTimeInterface::ATOM, (int)$claims->nbf) + ); + } + + if (isset($claims->iat) && is_numeric($claims->iat) && (int)$claims->iat > ($timestamp + $leeway)) { + throw new InvalidTokenException( + 'Cannot handle token prior to ' . date(\DateTimeInterface::ATOM, (int)$claims->iat) + ); + } + + if (isset($claims->exp) && is_numeric($claims->exp) && ($timestamp - $leeway) >= (int)$claims->exp) { + throw new InvalidTokenException('Expired token'); + } + + if ($audiences !== []) { + $tokenAudiences = $claims->aud ?? []; + if (is_string($tokenAudiences)) { + $tokenAudiences = [$tokenAudiences]; + } + + if (!is_array($tokenAudiences) || array_intersect($tokenAudiences, $audiences) === []) { + throw new InvalidTokenException('No acceptable audience in token.'); + } + } + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 942fe8465..67398610e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -16,6 +16,26 @@ require_once __DIR__ . '/../../../lib/base.php'; require_once __DIR__ . '/../../../tests/autoload.php'; -require_once __DIR__ . '/../vendor/autoload.php'; + +/** + * Register composer autoloader once + */ +$composerAutoloader = require __DIR__ . '/../vendor/autoload.php'; + +/** + * Register test namespace via Composer autoload + */ +$composerAutoloader->addPsr4( + 'OCA\\UserOIDC\\BaseTest\\', + __DIR__ . '/unit/MagentaCloud/', + true +); Server::get(IAppManager::class)->loadApp('user_oidc'); + +/** + * Cleanup hooks to ensure test isolation + */ +if (class_exists(\OC_Hook::class)) { + \OC_Hook::clear(); +} diff --git a/tests/unit/MagentaCloud/BearerTokenServiceTest.php b/tests/unit/MagentaCloud/BearerTokenServiceTest.php new file mode 100644 index 000000000..b559593f0 --- /dev/null +++ b/tests/unit/MagentaCloud/BearerTokenServiceTest.php @@ -0,0 +1,72 @@ +tokenService = \OC::$server->get(TokenService::class); + $this->accessSecret = Base64UrlSafe::encodeUnpadded('JQ17C99A-DAF8-4E27-FBW4-GV23B043C993'); + } + + public function testDecodeAndValidSignature(): void { + $decodedToken = $this->tokenService->decryptToken(self::EXPIRED_TOKEN, $this->accessSecret); + + $this->tokenService->verifySignature($decodedToken, $this->accessSecret); + $claims = $this->tokenService->decode($decodedToken); + + $this->assertNotNull($claims->exp); + $this->assertNotNull($claims->aud); + } + + private function decryptDecodeAndValidate(string $testToken): object { + $decodedToken = $this->tokenService->decryptToken($testToken, $this->accessSecret); + + $this->tokenService->verifySignature($decodedToken, $this->accessSecret); + $claims = $this->tokenService->decode($decodedToken); + + $this->assertNotNull($claims->exp); + $this->assertNotNull($claims->aud); + + return $claims; + } + + public function testDecryptDecodeAndValidSignature1(): void { + $claims = $this->decryptDecodeAndValidate(self::ENCRYPT1_SIGN_TOKEN); + + $this->assertEquals( + '10TESTSAM30000004901VOLKERKRIEGEL0000000', + $claims->{'urn:telekom.com:client_id'}, + ); + } + + public function testDecryptDecodeAndValidSignature2(): void { + $this->decryptDecodeAndValidate(self::ENCRYPT2_SIGN_TOKEN); + } + + public function testDecodeAndInvalidSignature(): void { + $this->expectException(SignatureException::class); + + $decodedToken = $this->tokenService->decryptToken(self::INVALID_SIGN_TOKEN, $this->accessSecret); + $this->tokenService->verifySignature($decodedToken, $this->accessSecret); + } +} diff --git a/tests/unit/MagentaCloud/BearerTokenTestCase.php b/tests/unit/MagentaCloud/BearerTokenTestCase.php new file mode 100644 index 000000000..a078c6330 --- /dev/null +++ b/tests/unit/MagentaCloud/BearerTokenTestCase.php @@ -0,0 +1,202 @@ + */ + private array $realExampleClaims = []; + + /** @return array */ + public function getRealExampleClaims(): array { + return $this->realExampleClaims; + } + + public function getTestBearerSecret(): string { + return Base64UrlSafe::encodeUnpadded('JQ17C99A-DAF8-4E27-FBW4-GV23B043C993'); + } + + public function setUp(): void { + parent::setUp(); + + $this->app = new App(Application::APP_ID); + $this->tokenService = $this->app->getContainer()->get(TokenService::class); + + $now = time(); + + $this->realExampleClaims = [ + 'iss' => 'sts00.idm.ver.sul.t-online.de', + 'urn:telekom.com:idm:at:subjectType' => [ + 'format' => 'urn:com:telekom:idm:1.0:nameid-format:anid', + 'realm' => 'ver.sul.t-online.de', + ], + 'acr' => 'urn:telekom:names:idm:THO:1.0:ac:classes:pwd', + 'sub' => '1200490100000000100XXXXX', + 'iat' => $now, + 'nbf' => $now, + 'exp' => $now + 7200, + 'urn:telekom.com:idm:at:authNStatements' => [ + 'urn:telekom:names:idm:THO:1.0:ac:classes:pwd' => [ + 'authenticatingAuthority' => null, + 'authNInstant' => $now, + ], + ], + 'aud' => ['http://auth.magentacloud.de'], + 'jti' => 'STS-1e22a06f-790c-40fb-ad1d-6de2ddcf2431', + 'urn:telekom.com:idm:at:attributes' => [ + [ 'name' => 'client_id', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '10TVL0SAM30000004901NEXTMAGENTACLOUDTEST'], + [ 'name' => 'displayname', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => 'nmc01@ver.sul.t-online.de'], + [ 'name' => 'email', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => 'nmc01@ver.sul.t-online.de'], + [ 'name' => 'anid', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '1200490100000000100XXXXX'], + [ 'name' => 'd556', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'domt', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => 'ver.sul.t-online.de'], + [ 'name' => 'f048', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '1'], + [ 'name' => 'f049', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '1'], + [ 'name' => 'f051', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'f460', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'f467', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'f468', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'f469', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'f471', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'f556', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '1'], + [ 'name' => 'f734', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'mainEmail', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => 'nmc01@ver.sul.t-online.de'], + [ 'name' => 's556', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '0'], + [ 'name' => 'usta', + 'nameFormat' => 'urn:com:telekom:idm:1.0:attrname-format:field', + 'value' => '1'] + ], + 'urn:telekom.com:idm:at:version' => '1.0', + ]; + } + + protected function signToken(array $claims, string $signKey, bool $invalidate = false): JWS { + $algorithmManager = new AlgorithmManager([ + new HS256(), + ]); + + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => $invalidate + ? Base64UrlSafe::encodeUnpadded('JQ17C99A-DAF8-4E27-FBW4-GV23B043C994') + : $signKey, + ]); + + return (new JWSBuilder($algorithmManager)) + ->create() + ->withPayload((string)json_encode($claims)) + ->addSignature($jwk, ['alg' => 'HS256']) + ->build(); + } + + protected function setupSignedToken(array $claims, string $signKey): string { + return (new JWSCompactSerializer())->serialize($this->signToken($claims, $signKey), 0); + } + + protected function setupEncryptedToken(JWS $token, string $decryptKey): string { + $keyEncryptionAlgorithmManager = new AlgorithmManager([ + new PBES2HS512A256KW(), + new RSAOAEP256(), + new ECDHESA256KW(), + ]); + + $contentEncryptionAlgorithmManager = new AlgorithmManager([ + new A256CBCHS512(), + ]); + + $compressionMethodManager = new CompressionMethodManager([ + new Deflate(), + ]); + + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => $decryptKey, + ]); + + $jwe = (new JWEBuilder( + $keyEncryptionAlgorithmManager, + $contentEncryptionAlgorithmManager, + $compressionMethodManager, + )) + ->create() + ->withPayload((new JWSCompactSerializer())->serialize($token, 0)) + ->withSharedProtectedHeader([ + 'alg' => 'PBES2-HS512+A256KW', + 'enc' => 'A256CBC-HS512', + 'zip' => 'DEF', + ]) + ->addRecipient($jwk) + ->build(); + + return (new JWECompactSerializer())->serialize($jwe, 0); + } + + protected function setupSignEncryptToken(array $claims, string $secret, bool $invalidate = false): string { + return $this->setupEncryptedToken($this->signToken($claims, $secret, $invalidate), $secret); + } +} diff --git a/tests/unit/MagentaCloud/HeaderBearerTokenTest.php b/tests/unit/MagentaCloud/HeaderBearerTokenTest.php new file mode 100644 index 000000000..3bb18bf88 --- /dev/null +++ b/tests/unit/MagentaCloud/HeaderBearerTokenTest.php @@ -0,0 +1,234 @@ +requestMock = $this->createMock(IRequest::class); + + $this->config = $this->createMock(IConfig::class); + $this->config->expects(self::any()) + ->method('getAppValue') + ->willReturnMap([ + [Application::APP_ID, 'provider-2-' . ProviderService::SETTING_MAPPING_UID, 'sub', 'uid'], + [Application::APP_ID, 'provider-2-' . ProviderService::SETTING_MAPPING_DISPLAYNAME, 'urn:telekom.com:displayname', 'dn'], + [Application::APP_ID, 'provider-2-' . ProviderService::SETTING_MAPPING_EMAIL, 'urn:telekom.com:mainEmail', 'mail'], + [Application::APP_ID, 'provider-2-' . ProviderService::SETTING_MAPPING_QUOTA, 'quota', '1g'], + [Application::APP_ID, 'provider-2-' . ProviderService::SETTING_UNIQUE_UID, '0', '0'], + ]); + + $crypto = $app->getContainer()->get(ICrypto::class); + + $this->b64BearerToken = $this->getTestBearerSecret(); + $encryptedB64BearerToken = $crypto->encrypt($this->b64BearerToken); + + $this->providerMapper = $this->createMock(ProviderMapper::class); + + $provider1 = $this->getMockBuilder(Provider::class) + ->addMethods([ + 'getId', + 'getIdentifier', + 'getClientId', + 'getClientSecret', + 'getBearerSecret', + ]) + ->getMock(); + + $provider1->expects(self::any())->method('getId')->willReturn(1); + $provider1->expects(self::any())->method('getIdentifier')->willReturn('Fraesbook'); + $provider1->expects(self::any())->method('getClientId')->willReturn('FraesRein1'); + $provider1->expects(self::any())->method('getClientSecret')->willReturn('client****'); + $provider1->expects(self::any())->method('getBearerSecret')->willReturn('xx***'); + + $provider2 = $this->getMockBuilder(Provider::class) + ->addMethods([ + 'getId', + 'getIdentifier', + 'getClientId', + 'getClientSecret', + 'getBearerSecret', + 'getDiscoveryEndpoint', + ]) + ->getMock(); + + $provider2->expects(self::any())->method('getId')->willReturn(2); + $provider2->expects(self::any())->method('getIdentifier')->willReturn('Telekom'); + $provider2->expects(self::any())->method('getClientId')->willReturn('10TVL0SAM30000004901NEXTMAGENTACLOUDTEST'); + $provider2->expects(self::any())->method('getClientSecret')->willReturn('client****'); + $provider2->expects(self::any())->method('getBearerSecret')->willReturn($encryptedB64BearerToken); + $provider2->expects(self::any())->method('getDiscoveryEndpoint')->willReturn('https://accounts.login00.idm.ver.sul.t-online.de/.well-known/openid-configuration'); + + $this->providerMapper->expects(self::any()) + ->method('getProviders') + ->willReturn([$provider1, $provider2]); + + $this->providerService = $this->createMock(ProviderService::class); + $this->providerService->expects(self::any()) + ->method('getSetting') + ->willReturnCallback(static function (int $id, string $field, string $default): string { + if ($field === ProviderService::SETTING_MAPPING_UID) { + return 'sub'; + } + + if ($field === ProviderService::SETTING_CHECK_BEARER) { + return '1'; + } + + return $default; + }); + + $user = $this->createMock(IUser::class); + $user->expects(self::any()) + ->method('getUID') + ->willReturn('1200490100000000100XXXXX'); + $user->expects(self::any()) + ->method('getDisplayName') + ->willReturn('nmc01'); + $user->expects(self::any()) + ->method('getEMailAddress') + ->willReturn('nmc01@ver.sul.t-online.de'); + + $userManager = $this->createMock(IUserManager::class); + $userManager->expects(self::any()) + ->method('get') + ->willReturn($user); + + $provisioningService = $this->createMock(ProvisioningEventService::class); + $provisioningService->expects(self::any()) + ->method('provisionUser') + ->willReturn([ + 'user' => $user, + 'userData' => [], + ]); + + $this->backend = new MBackend( + $this->config, + $app->getContainer()->get(UserMapper::class), + $app->getContainer()->get(LoggerInterface::class), + $this->requestMock, + $app->getContainer()->get(ISession::class), + $app->getContainer()->get(IURLGenerator::class), + $app->getContainer()->get(IEventDispatcher::class), + $this->createMock(DiscoveryService::class), + $this->providerMapper, + $this->providerService, + $userManager, + $crypto, + $app->getContainer()->get(TokenService::class), + $provisioningService, + ); + } + + public function testValidSignature(): void { + $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->b64BearerToken); + + $this->requestMock->expects(self::any()) + ->method('getHeader') + ->with(self::equalTo(Application::OIDC_API_REQ_HEADER)) + ->willReturn('Bearer ' . $testtoken); + + $this->assertTrue($this->backend->isSessionActive()); + $this->assertEquals('1200490100000000100XXXXX', $this->backend->getCurrentUserId()); + } + + public function testInvalidSignature(): void { + $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->b64BearerToken); + $invalidSignToken = mb_substr($testtoken, 0, -1); + + $this->requestMock->expects(self::any()) + ->method('getHeader') + ->with(self::equalTo(Application::OIDC_API_REQ_HEADER)) + ->willReturn('Bearer ' . $invalidSignToken); + + $this->assertTrue($this->backend->isSessionActive()); + $this->assertEquals('', $this->backend->getCurrentUserId()); + } + + public function testEncryptedValidSignature(): void { + $testtoken = $this->setupSignEncryptToken($this->getRealExampleClaims(), $this->b64BearerToken); + + $this->requestMock->expects(self::any()) + ->method('getHeader') + ->with(self::equalTo(Application::OIDC_API_REQ_HEADER)) + ->willReturn('Bearer ' . $testtoken); + + $this->assertTrue($this->backend->isSessionActive()); + $this->assertEquals('1200490100000000100XXXXX', $this->backend->getCurrentUserId()); + } + + public function testEncryptedInvalidSignature(): void { + $invalidEncToken = $this->setupSignEncryptToken( + $this->getRealExampleClaims(), + $this->b64BearerToken, + true, + ); + + $this->requestMock->expects(self::any()) + ->method('getHeader') + ->with(self::equalTo(Application::OIDC_API_REQ_HEADER)) + ->willReturn('Bearer ' . $invalidEncToken); + + $this->assertTrue($this->backend->isSessionActive()); + $this->assertEquals('', $this->backend->getCurrentUserId()); + } + + public const ENCRYPT1_SIGN_TOKEN = 'eyJwMnMiOiI4VzhYY21iaHJPSSIsInAyYyI6MTAwMCwiY3R5IjoiSldUIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImFsZyI6IlBCRVMyLUhTNTEyK0EyNTZLVyJ9.5bA_ctLbQOnMojJW3MPo83AIvCAu3MpmaaD7j2GzqBv5_-D4w69ONqcPEsc6LYMG9B-rw3HDXng4Mqye4KqpW70ECpf9HXV6.6zl4Zqp4wbcO_AqqmpA3sQ.y7dHcwxXveYkuh4UaqHhE4nvP_avZsxaf7aAbnJdDHHKbBKvEKKqHkPg593i14ypWuRHd2i9Opsuyppfxx9Hw7C7N7LJ8UCTYMihHqlJkHecB08xgJ3ciE0L2Qtvg9hfxQbHNVV4p1_KL3ubAXt9ovwDCOJvN6PXyixUDtYYF1D_Km7Ze1ptUNbwS2H4vf-MKHwwrm5uhTvXOppGNO-0tYnIMOZ8BkiTtrrlO6IQbRcC4EMw74PzbFsQXY9u1xsNZ9IOrzbBl_EyPBLr5ool1BGlvNog4XFsHLgxUa5cjIcZVRMgZSLWdToTiXYFAWdO6fbQrRWT8ERRDWjiDxJEaPlfI_61G5NzJN2NKnSAY7fR8i3Rfs_JoF1TtpR5dGU28Lk1vcLjKYBLqp2hjW97QsANVgmalkkJMUpiAvNN48ZSCK9T3vTfiH7unFRNWvTKvZXyHIkYQPZ0-b3Z9s5oLMx93Snvcq9jQVKA1dWU_bEUIOnwP65ADU_FIkYB8gsZXp5Za3HrK63u03Lij6rwkJpEPbwcnxhBkMhtKOOwQVZm1ZBf_lVyn39MFXmLN_gDD052vFpxl1NnG0KEg8XJQ_usE9e64q7W6IG4gRm9NYG6rdeik6Dm45K8fA4oUiyjdgHjveR6GW8uXQR-tWXf3IC-_2jws2PJ31acdoEbDU30XlVeCqENW-ylPJ10rP28XxboQVJMRrzMiEzu39IH3c02czHh81U09TREVsO2S8CCQcahboaplDg9kpr1UZpsRrjg40bEtdm2cKubTbczGiXiF7sI0qE-kHm0aiK5c6mO8fHETMCmvh2vhxcYo_T6q7VklbwiZVbn47z-oriEDyPlLrB_PzYR6fNRbtObttj0CHRgf-NI69RU2pAGxujSi2lEhNkG-CAFNfASKm8uSUCg8UPr7v38c5vr4IuYC1gYjxgebXIh0EFX4G8jZM6ljPSzmMFDyErWJQ5OrtJjuKrUa96Yp3oOZTemtCwc--mrDXmpwVlaBMCuuJDz6zucxwSeVK0mP0t56zHeK59jxz0OfV62TrcVeZaLqSl3o-pVsY5KrLxL1qf2QIry-uy_c1zi9AuZnSbH3t1RvmyG5-QIh5WSPOLXG9ivuHKAdQTvBnchXWfkUVkoPYuPFyBydlPAhpRQyBLHboqdT6lIdoQ5lBRI8vsGb9wQVSQx08hbpEFOPMe-SJqzjZp36sUurJrgj_ethbIWkTSe_HPkcvBv8X0kyvhnyTKYJoroE5HDM0dtgFW8xK8NmOZOuREzJW5fpqzJML8iY0p1IX3bvGrCeVMEJtM0T6KSJFdPHBAzkWNNMBUc2jhuxa6B2cSaMz60bwSCw8n5NWz8wkXUFJJkHKEnK8tFbtOQXHeGG48k7Wl6kgrQkAFAHZqQt9gRDdmGcYAYHVK7cESjABV9LWQIQYy0eyveU0sWE5sYXKCwsk8rLiKt5GmZlRQ0rOltuFXRTu_EZYuqR0DCRXrjQWVN1zLTy0LMqAvDR-PJcFtekbT9CXLEW6M6GHzJhYfNyMc_cPitG8QwS5EWGzJjQIiNsJBRyV7cPlHeMhKzDtEk3DR3l-qQJa9-54RQB-kStJjB0AAZ21ku7eBS6orT0lljj935eghlHxAzyr1fvlDjIpHc--ob_7DOPc9sBGqcwdYoZ28zD1d02rpJujOwTe4zgll4vffJ_aFP8hm19pmroCwFsZPWIK6GN_cllJaxnllkJ_9c-7eBj1rKkNX0DLyNwKoMYttugeQFWAxaaqWhoOpQXnRHaVt5hTzoexi5C2j_aVBUAzyMPZtvuYgY1uc8zeKt5X8rAy3Y7WqYeOy8Q6IezVyTE6p0kzYgzUT1Vg2XZEr7dBgNkv8ySfYQNG5d8_PtvBHX-SOy25rtes7oUHHgZx0AkpomhNGSwfrW4dyIWCa6j5qUexqs3TPip_FAJwdW38OnyfPQ5SHLTt8D6OCOLN70MdbPpeoFkGnx1oj1Xjx_UW8mtueWAkxidv6Lamf_D5j8sJvkksne8Nos2YvGNkaGZwQK8YfjvPP-VVdukLMqoloovOuvgxLVLSvnDYcRRjfwAdiKwFNGdMbdV5LwfAzVAlncyWPJso3Lk9fPYd88YW8e6o7xiboiushcbDQU0ZN_Zh9YGk-8R4VnvAuI3yWxLrBB8NFUwKYkNBupVWrxRHJbJEebsLv9r_PZstBHHfMFpcQYX05NYfQiezhQ9l-aseC9Ay4FLbcxyXkIiPEBfiwZESqQbYoL3OeBQYzsV8AFe4GVdUUwPCuPjKR52UlkPiUJthxGkLFfcEPbqfX_lByN5YZRMSruOt6yKysbBIw0gcC6n7wuA_URaFNSPfyHe6nqAtveh1YjZpwZszAERyk2ziFXKFYFppdjMPvxF37uWoH_BEpv9Bs7yaxPRK7pfniS105RBsDFS093-3sUYM6W7IrmPfKAe71OtdWtQQqQKOAX3WGFShCIKyz-aOJWJPRG35Q2DOGu0nehFetGVsSnt-ehmru-Zuv4IanlF0_3SjQ7l7l6gg3Sfyy6sN8SVvxTtw4jLkaAM6cpmVMQVP8uQeJ9IFSHyq1kFceQcguh5tbwMknJzcMNzmZ9zEOG4ifyk9zmeulX9Rtf3lIXIOU-1lEs5bVm42eg1IKpxaY8PeTrT4qvPIyVkOprpKGIAcGyD0tP11vvDCvbltEWBo72gdbtD9tUdUPK0XRD_TgEPy2YU6I6BsKBStd40Fk6nOCGrq-mjYmH6OK3JUF3EVV7E0fEg7BgnYPLxcla0l7H6LpY4sqmFwapDqknjhgbqK0dyZDGWEPJ7Ph_5K6BazKuV_1bf6ZFOuRbm72cmT6vAJM8BhihAdTQt92QbTPikjLS2he5AfSV1ieDgLT26dsLNuLkyExyBqUGkrFoojh4fvW9K-wDKtgvQwCYZYABlC9JY72gtpaV2OV2UrB4aXuJX6n1NNXaSzpPqSupAIGK3Gaw39yrzBgBjTYAe0nnRu10BO7-gNRvKGIMCBTa7c-c0o0eNGe81xv1w8_-6auoKZYS8rzXQ8T6XLUjC1mRZD_cGxnfEra2G96-Cqm9WZO5hVX5fpXZhybz7neyGKlUKZG_An-jGmc9j_m03-5EEOfKAXJNlmOT1IynNVudtzTTrh8O5Dp4nD6fKsyOrg-6yRePCiP4FeItLCH6uVLWWdR65WZzklQuPrBELg58OzIsaBuKCKNjODSA4dGVE4JurhmgnnSmaqz2z6s0Zd1gXERebk_1WEmkWd03jO7dXMk3hOM9zV9BrZALOAll3GsvCqgh9kfouX-3ZNSNO7Lah6ecLD_zK228ap6r1MeY2VK-PiHUEnH58jh2HuutZB1Ge0GVvsYBue_r0FjGVNh6a9XYwIaf1Um2Z81WgHpWHZ-pLVZlkbN1vxgqLNBpjDy6UWpPJzOUv829C31WID92Wa6XPsfq6sIvYRUEx03DE2sbXKjUNX2t8InuLCgC6_wmq-GOoZ5vLKt1KHMicJUM9YFZYYKd-7c25X6DLplAnP-Hw_URgRINQdD8kOWzZ_70SiEq0om6OWniva6czSiwrcml_UBDA5Xr8pNtSWqtNbHh1LJzJenVIZl9gPLRs_o-OxB9gylqk7HwQZgKPCbvccYyh162Iy_Kg2j07hnDuoiUyZ93o9x_3Asf8Ms_E_ov6CqpFgKICX6rEE0oOgFO_pKvwtNH8fF-uNkVGKQwNYX6S33SlWh_pULYLSl-YrXVP0hLLmGlunnOGXUIVTXjQcc6AheR8Dmg9jDIefpgHMH6hegAnoZL0_AVuG-yd9LSRSh2qH_rABtJHTOx-0qQ6yYnrzHcMuvatCwDuIePK5DcxBj8KhKq9F4y_i5Ym9drIskRvAzwygZuIIuT3uyXl5nI6YE_jd6F9w4PZ7SkOs9JvfCnt-Wm7UKI6dxLnCRoTarUwop1wDZ77-rRwYoo5zYwF73BragZBZuWNB8ImLlktcAyCBF6P2_F2j4jvnQNLShYZ5HsJKsJNljjIiKYEAeJ2ScT2tjPSfMsdssWQPPByDgwnWtGpx2z6JTFGLUHaj_WbQe3hciyl7jGM2U1JrA610-Jb0X_OiGslZuYBasmPkEXFbDhZy_QZ4Pjs4RddBqrS15-H4FphxsB4knYHtfAzvJno80QmR69zvIfBSIScEx48foHjbeObNpW51IGbg2-yhssa9YtLpjpafnc1-yJ5xj6tJWYZcpskhgADRQvoxF8Xa7BE8o0D9-I7r2Yp0wMfYrbX8NCTBUWczxBZt2juBIERwgjHZzphIGVXNJ6ARm9F12UMf2OwUEk56J6SiSfB1ho7EDdARwj6Nfkm1LjpYLDhii-IRVJUN8tphw6SHVJBbMucYsXsL8viafUwdh7MbBwLKOPgZM4H9BqWFePgEglf7nzrALd2WV40tOai-sm4e4UCKh9bQ1qNw-uHQLP81NNzMA.bMWJdVmxAg2RZm7NE9wTz4H4LwjDb21tFV8hGtTKGFI'; + + public function testEncryptedRealSignature1(): void { + $this->requestMock->expects(self::any()) + ->method('getHeader') + ->with(self::equalTo(Application::OIDC_API_REQ_HEADER)) + ->willReturn('Bearer ' . self::ENCRYPT1_SIGN_TOKEN); + + $this->assertTrue($this->backend->isSessionActive()); + $this->assertEquals('', $this->backend->getCurrentUserId()); + } + + public const ENCRYPT2_SIGN_TOKEN = 'eyJwMnMiOiJWSTRQS0ZCeVRyUSIsInAyYyI6MTAwMCwiY3R5IjoiSldUIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImFsZyI6IlBCRVMyLUhTNTEyK0EyNTZLVyJ9.YQlaJwr-og6DNQhCkszfsts2z2NLuWsP5czCbMQdyhqjBuhutAvdZlqkFD6el4OeupoXXkTb7XkNyNZVq5S-rfUNGptv27J9.mNCv0KWUDXJoVLxkyppGqg.BdjbqWD14kmuJfLhVMWInuDjTh5O_qxjF9n9rD3viGH1WXZvQtiPT9U2ZKN17jLyzhLXtmvPP_bGZZPrGc5p68WoAteCSxzwJRGcF0hzO6gBhgvx_CcddG0jWcfaXgsFbOeLBpZMKR3w8_6I6shxDcrm0vwL_xeSOd_m4me_VVPQGkaOPKrMy4Ywlh-H7DTquI4NgC1vqt-B7Mpowj82PifFSgEDVrFPkNsustl4PE_2IiL5s_YAPme-OKq50wXzjcjsKAWEbgfsTk5iPoEJNaNWPyWUKiQ8Zp3w6qQgsiY7EGKB5D_-cgbkpq7GmASTiV0FbWHlKleQmHlZ0yJe-WMn0Ai_feVrNwsDM1X5QJ0YMyk5otef-s_64vnLCyo4VbLexO3d67dUqut03xdb9c2SLrupLzpONAJ-nNJ2vNbfr2EBZiSHYjttsmRXlAXgRhiJZIdUGDxBJO-ydEaR22VtPK8pdX9s2Sv8t609xeNQA9hjxCT6IRtEv7vJ0sODV-LSJetO3RKYdBOzNUUvz5VHDE6ogLWNF5blvQ8JoImJd8XP4rNmasassb1NHOPFr4lO7r4ZIn4vmb_idBjzWO2940o48vO5MoRT9gN9rUZDhTwK2enuKdek10PmsVIII5Q18DwvDZhRfM1ZbqZRdkKpnkVb-nWqXChHcSgFcR-TXZGmh3WaH6OJWKpckBAoQ1OHZDl2h_lIfCJ7-eOHR2i3tpXEp6URi31iABcsUZniv8hxB1XYORu9Bl63BQ_t6ns3L-wlMb-LAcvk_sruyObIAuhiZzCyJGxaugje0znGMd3vSXi4U-oqnuGKlKu_1-o7-qB-f1Pkfl6UCk5mS6Vnq-P78FN0iIGaeT8FwsrX-uAFpO8HH4YYEeE8yTi0CQShXVYPiisAQIQFg6QBjy5zEXUZnMBfG-iQ4lfxBJg2sGZ7-HAZpYB2RXDVXAUi4fqI8A1RdHpQofqFGyQZtfVPviOhfNw9Xx79GXb7Cw7viaHFFeyocbyk-55bqjRKpWPP758oxsmP7LZn7yVbMRciCiGDB0LNA1_vJ-7qi9oIUFGdoEW0r3y9I8Su3TH2H2P7HjVaIojOwY4z5_EuADg3lzoSACPvR_I7_r5zMqm7g89HDOo7b-_wh46JVpORbCemQwvJQehN6MUJTbBv_rLKCJ_wjNNMF9sa29yUvoUmEFvlLLy2e2p_r-4AnfGP5P1givxxh12pS_c64XZ1SLqaALTARRwkv1HCnufTNmit80-5rRghgAANf4KXcppXDoMqKW-mrI1Q_ckrkVb7vJuEHaPB1cka5MLIpQ9dFz2iwAEZcFDXXpx2u_ySSDSzRItgazuSOk7DMJzTER_aMTOP2IwzVPoGK8K3RT7wS0lNGfalepX-BAcAbZz4md2PAgHPcfKt1czhdBO5DO9mhKLSSHNA2cc4MmE4_3Ir3BfQCL7mQExvy5mESVr05eTIvLBAzae6SimwzkAUz3o6sxU0neTfxyM47zwQYutOvyC5MCHcA00HdLcyRG9PaE3Bsu5n1WJpIY8i217eFvBZXTIBM-b9vS2_lfC_nNC9DB4N33B2DFEkH02uk9L8vOY90vunGKX-qLXahFOWV_WrFxi_jKzav1FIGV0FcK8QPU8UC9tF9cbxKE1DyLu_G1I9XHP8KO7y9bKOGNv1sRDSUiGZX1_COPM6cifpJsEhOLsucmGsybKg2C77cXhuou9OSen89Devr2ZzWtSZOg1HQdAJuFVkQhjAKcygW49mKqvXsUytRkWEN1mOPsuIJgmt3t4-bxvxeH9qITjy7gR8KYCY5sgdeaIhiEmc2hVp1cBo_HMQNo1E1ew0l8K5X1gavEbUd3RCcRBEtsekwTsfGFoQ6rivH_F5PwAlhMde9jN-I3fnPZMlPnTQEBpb3RdcPV8YNJ7RzRVbQJktdDqb_be1L3BYzKuK8hnv4aEu4Y0wYLRkBxYNIW70X06bIeyCC7B07xn5yLrUaC0MS4UxO9gSPEdauj1OBP7Z_va7zNIbOr4CI68QLfUwtoWpYLPag1exLADeQO3Cdd1qX9LU2trhNVNsw_NapqVkguAI3A3YTuaCQpt68kKGhsugiJ7DsxHuWoNzou4hejBQAvJ1Lm-N38DFKB47gDrwraafpRAezpCyclpaQeYbMK_rz12YCbl35PkFqDefL7B4EESJyk_Wzqpl6Y3AU81rrXK2aVaO0iuVuunWc492tullX_TQ4rtcX_URyZBKz9eF6dxwMJM5UNTtnz7uq-oOmxL3o80XSLpSbfHM4p9elkZGsXfsgpPj0DQJ7EAneLGRqncdLC-6d_ry2E5HwtcC8iWS51CFttDoVyatDDdEWOB7WxD0wy63uc8XK58PPc_ped8W53bid3jB2E5Bg0_c63KQ83U7fezzMtFhUzLIc83FzsG9D4hAPGvZowj3IOAh-E1FlvjvHThse_iH2lIoA1sC9WHpUFx3RkalAaN76fAWP-3xO-bckk9AR3XX1pPxYnx0kOq0a4GR9G7y_ylBt6zGZ0E8TUg8VHS5i834V_rh15R3o8pHncq8b7kwAA--EWCuiLP8B7gTgMqS58r9G89PfZa7u9Wf4NkjoBvZbKzfbnZmPzXkuSLPyC4VBcAp9hZSzdTTd67zLYikGij5dSZ3TRFFG6MSvGDBYvs2P9KaixhcJbY6a7ULGbeBpB29rnq4OEXoGMjOoyG171ZzIeuXAvZnk_ujhEWlCFvznvfQu8H5mTjtFb17I9BJ4YS5gT3E5UwHEH_bAaJI8KtRjfbhKkv09cxaYqRjCMoPlLEPnwDxc2Ousux5SHOjgIqWp9z1acIUzLqkbK3euZNL1YpCNRJTMn4qDPhel5gyY9IjoqgEhfQFJ4ckp2_DLGcFZj3Wwwh-WGmkduvTr2TE_kIA-SmXcqwyGdLse3n7JUHVxcumvXgr5oxe2I_h6UQGSPLxz-KwKxeIUAARQhM9f2mjBcnJ3hkaJj-ciuAjof-WBVCZJsjlccogXhXtxLbjz8ZSntQuaLdjb-ci2wMANhPWnWh9R2KqnREhp-PTllAG4Bj-BWmpzTTRy7tZGkFKoL1xiZMCFA_5egS9V1lqwz62BVOVZ7AeZ5NK8hjGnzSgq6E3bhLoTDupPJLUl3f7fC16PqHQjb049Srme9lK13s8oR79g9UUufW-jQloUhA5fRql45ArveLSTSgg-nUCk22Dso1-Cjk7BIqsEFmeBcyhQoqpjCiuKT6iiVTuEnQXAJ8WEi_hJKTXJ2NxEOdaCG1VaZNycggvX4urmkD53HLpXABitdYpBqJvu-DkO-K8OZA0v8tThBZx4zrIY5EMUPi9YikMrWOqeJtXhA6ZYpeUjK8FHM-sAb3i377lw0CarC8XDzzeNCHRJvaksZdhviuBqNjWXQ_VtU6xEqXsXc8FSftvK2SoSiW19qgiQkrUMJxSy6A_daXT0b7FucBACN1O3YDQ2-x6juM1uMjLico4I1OeFP0RsbUazYVdW0wL6CXiC81ygyTk_XE85xyWwNyiooBuJc377qapNcbUVAYca6R5YVHLVsVLjr3h_BlO1KWv064dypH1faO8cYatSwXp5ttcUg8xoI6E_q0N3IUepfTleZBiCRncoFyKcOT7xUlqojhkC4YirwgtV5Pv3hp6MQ9hjibUeX8mNLFepE1tDFyzZmMXM2kr0Q99WVINbRqv8vGjt82wuZScuJiBy8P6BV-FJLAXsECrAtauSQlDP7YTWsibeqQ3_LEDRd4G9BMj7RorJg6Z0jFloIVfzQOHkZCEZITbh8ifrDrnpMO84l-__kRVImb1rW6I-1KdTubMAaZbAYPhpiYWmC5FJfmyyCSA7uuqeP7RWSm3fZeJK-YinLKH6dUHgwchPQ1godY97ywznP5YuM9pmve75iaNcd3ILuljGx8eBj2Ig7lkPK00JId6FfDwfg9h9cgAKfqueZRBPEN0D3grwZkplG7-_6B1ZhmwjRHaFY88L4EUVnqNh9F73190G-oOuM8Ztw0ItfLU-EvshvMLZ_4W-FUN8B_okqAGH0F088j5ZADxS7HdWMq0DNDIaXpDgPjPhLT7mng20O7BWfG8nTSMEqTBGfvpgoeTL5LjBuDESG4H7FhxGXlfum8asCs8WgdhZ0Zh-SRV8bcLTcpOSEuutdCOK0DxMjs30MTijfLDfpHQP9_fWuG__3n-9g-7Rs6OIaU9jwJ2yWarC-CfPX7yzZcgcsAbT_UEHqRZXQU5vhepV5tmvM5RTv9k7a16b6xIEJIBNLaDRw7LZaauowiaF40vrMNZNGnqqTED_bqMcnfYXvp2R0QFZihNgey1rh2ndhYcSmXSC0F4Wm6r4T6q9VfW_T4Y7NGb31a001Mq_edR2xa_uSBETzybCsHNUq5bD_F3Qj4JUivq2nyh-UAbxP71MdlGE8RN5RYL7b5j25o1oyw5tSYbndIjfp_oVHkdWtnYJsH6T131lUwM0-DwMWWtLParbukDjDjy08aTEDR0vW6LaJJ9bh1_Po-XR6sG4lAeTcJo7XjptIWQCbkSrV6gD7GXOOJgF2qVlvM02ARNLl6DNo3Y7ar_H4LkZ3aAkkV1Yy7-vnVpIEx-UoSnilNRQN_rp6icTwNilt1UnuuLutxKISHRMDP3Pv9vEATDQy-z.w6KkNgIIeh8SPlMtA6l7dbywsDAKFLkTmrVc65q-BL8'; + + public function testEncryptedRealSignature2(): void { + $this->requestMock->expects(self::any()) + ->method('getHeader') + ->with(self::equalTo(Application::OIDC_API_REQ_HEADER)) + ->willReturn('Bearer ' . self::ENCRYPT2_SIGN_TOKEN); + + $this->assertTrue($this->backend->isSessionActive()); + $this->assertEquals('', $this->backend->getCurrentUserId()); + } +} diff --git a/tests/unit/MagentaCloud/SamBearerTokenTest.php b/tests/unit/MagentaCloud/SamBearerTokenTest.php new file mode 100644 index 000000000..ff1edfd22 --- /dev/null +++ b/tests/unit/MagentaCloud/SamBearerTokenTest.php @@ -0,0 +1,61 @@ +expectNotToPerformAssertions(); + + $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->getTestBearerSecret()); + $bearerToken = $this->tokenService->decryptToken($testtoken, $this->getTestBearerSecret()); + + $this->tokenService->verifySignature($bearerToken, $this->getTestBearerSecret()); + $claims = $this->tokenService->decode($bearerToken); + $this->tokenService->verifyClaims($claims, ['http://auth.magentacloud.de']); + } + + public function testInvalidSignature(): void { + $this->expectException(SignatureException::class); + + $bearerToken = $this->signToken( + $this->getRealExampleClaims(), + $this->getTestBearerSecret(), + true, + ); + + $this->tokenService->verifySignature($bearerToken, $this->getTestBearerSecret()); + } + + public function testEncryptedValidSignature(): void { + $this->expectNotToPerformAssertions(); + + $testtoken = $this->setupSignEncryptToken($this->getRealExampleClaims(), $this->getTestBearerSecret()); + $bearerToken = $this->tokenService->decryptToken($testtoken, $this->getTestBearerSecret()); + + $this->tokenService->verifySignature($bearerToken, $this->getTestBearerSecret()); + $claims = $this->tokenService->decode($bearerToken); + $this->tokenService->verifyClaims($claims, ['http://auth.magentacloud.de']); + } + + public function testEncryptedInvalidEncryption(): void { + $this->expectException(InvalidTokenException::class); + + $testtoken = $this->setupSignEncryptToken($this->getRealExampleClaims(), $this->getTestBearerSecret()); + $invalidEncryption = mb_substr($testtoken, 0, -1); + + $bearerToken = $this->tokenService->decryptToken($invalidEncryption, $this->getTestBearerSecret()); + $this->tokenService->verifySignature($bearerToken, $this->getTestBearerSecret()); + $claims = $this->tokenService->decode($bearerToken); + $this->tokenService->verifyClaims($claims, ['http://auth.magentacloud.de']); + } +} From 069154d588e44d831874d681ce8ac8930d39570e Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 12:31:25 +0200 Subject: [PATCH 29/57] fix code style --- tests/unit/MagentaCloud/BearerTokenTestCase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/MagentaCloud/BearerTokenTestCase.php b/tests/unit/MagentaCloud/BearerTokenTestCase.php index a078c6330..7ee2d19c7 100644 --- a/tests/unit/MagentaCloud/BearerTokenTestCase.php +++ b/tests/unit/MagentaCloud/BearerTokenTestCase.php @@ -9,6 +9,8 @@ namespace OCA\UserOIDC\BaseTest; +use OCA\UserOIDC\AppInfo\Application; +use OCA\UserOIDC\MagentaBearer\TokenService; use OCA\UserOIDC\Vendor\Jose\Component\Core\AlgorithmManager; use OCA\UserOIDC\Vendor\Jose\Component\Core\JWK; use OCA\UserOIDC\Vendor\Jose\Component\Core\Util\Base64UrlSafe; @@ -24,8 +26,6 @@ use OCA\UserOIDC\Vendor\Jose\Component\Signature\JWS; use OCA\UserOIDC\Vendor\Jose\Component\Signature\JWSBuilder; use OCA\UserOIDC\Vendor\Jose\Component\Signature\Serializer\CompactSerializer as JWSCompactSerializer; -use OCA\UserOIDC\AppInfo\Application; -use OCA\UserOIDC\MagentaBearer\TokenService; use OCP\AppFramework\App; use PHPUnit\Framework\TestCase; From 56ff33aed0685d5ea0d7927e4daa2ff3d0d7fa81 Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Wed, 6 May 2026 12:45:32 +0200 Subject: [PATCH 30/57] Add telekomBackChannelLogout route to routes.php --- appinfo/routes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/appinfo/routes.php b/appinfo/routes.php index faf0ae16b..91f8656b7 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -16,6 +16,7 @@ ['name' => 'login#code', 'url' => '/code', 'verb' => 'GET'], ['name' => 'login#singleLogoutService', 'url' => '/sls', 'verb' => 'GET'], ['name' => 'login#backChannelLogout', 'url' => '/backchannel-logout/{providerIdentifier}', 'verb' => 'POST'], + ['name' => 'login#telekomBackChannelLogout', 'url' => '/logout', 'verb' => 'POST'], ['name' => 'id4me#showLogin', 'url' => '/id4me', 'verb' => 'GET'], ['name' => 'id4me#login', 'url' => '/id4me', 'verb' => 'POST'], From bfa16a83f7c933c9d4ec9704ca98c30139becaf4 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 13:09:55 +0200 Subject: [PATCH 31/57] added central customization --- composer.json | 75 +- composer.lock | 2293 +++++++++++++++++++++++++++--- lib/AppInfo/Application.php | 57 +- lib/MagentaBearer/MBackend.php | 148 ++ lib/User/AbstractOidcBackend.php | 192 +++ 5 files changed, 2519 insertions(+), 246 deletions(-) create mode 100644 lib/MagentaBearer/MBackend.php create mode 100644 lib/User/AbstractOidcBackend.php diff --git a/composer.json b/composer.json index 49b50b80e..83f6e2ae9 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,16 @@ "bamarni/composer-bin-plugin": true } }, + "autoload": { + "psr-4": { + "OCA\\UserOIDC\\": "lib/" + } + }, + "autoload-dev": { + "psr-4": { + "OCA\\UserOIDC\\Tests\\": "tests/" + } + }, "scripts": { "cs:fix": "php-cs-fixer fix", "cs:check": "php-cs-fixer fix --dry-run --diff", @@ -18,22 +28,32 @@ "psalm:update-baseline": "psalm --threads=1 --update-baseline", "psalm:update-baseline:force": "psalm --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml", "openapi": "generate-spec --verbose", + "post-install-cmd": [ "@composer bin all install --ansi", - "\"vendor/bin/mozart\" compose", + "vendor/bin/mozart compose", + "@fix-prefixed-aeskw-imports", "composer dump-autoload" ], "post-update-cmd": [ "@composer bin all install --ansi", - "\"vendor/bin/mozart\" compose", + "vendor/bin/mozart compose", + "@fix-prefixed-aeskw-imports", "composer dump-autoload" - ] + ], + "fix-prefixed-aeskw-imports": "[ ! -d lib/Vendor/Jose/Component/Encryption/Algorithm/KeyEncryption ] || find lib/Vendor/Jose/Component/Encryption/Algorithm/KeyEncryption -type f -name '*.php' -exec sed -i -e 's/use AESKW\\\\/use OCA\\\\UserOIDC\\\\Vendor\\\\AESKW\\\\/g' {} +; [ ! -d lib/Vendor/AESKW ] || find lib/Vendor/AESKW -type f -name '*.php' -exec sed -i -e 's/use OCA\\\\UserOIDC\\\\Vendor\\\\AESKW;/use AESKW;/g' {} +; [ ! -f lib/Vendor/AESKW/AESKW.php ] || sed -i -e 's/trait OCA\\\\UserOIDC\\\\Vendor\\\\AESKW/trait AESKW/g' lib/Vendor/AESKW/AESKW.php" }, + "require": { "id4me/id4me-rp": "^1.2", "firebase/php-jwt": "^7", - "bamarni/composer-bin-plugin": "^1.4" + "bamarni/composer-bin-plugin": "^1.4", + "web-token/jwt-core": "^3.4", + "web-token/jwt-signature": "^3.4", + "web-token/jwt-encryption": "^3.4", + "spomky-labs/aes-key-wrap": "^7.0" }, + "require-dev": { "nextcloud/coding-standard": "^1.0.0", "symfony/event-dispatcher": "^7", @@ -41,29 +61,34 @@ "phpunit/phpunit": "^11", "nextcloud/openapi-extractor": "^1.8" }, + "extra": { "mozart": { - "dep_namespace": "OCA\\UserOIDC\\Vendor\\", - "dep_directory": "/lib/Vendor/", - "classmap_directory": "/lib/autoload/", - "classmap_prefix": "NEXTCLOUD_USER_OIDC_", - "packages": [ - "firebase/php-jwt", - "id4me/id4me-rp" - ], - "delete_vendor_directories": true, - "override_autoload": { - "id4me/id4me-rp": { - "psr-4": { - "Id4me\\RP\\": "src/" - } - } - } + "dep_namespace": "OCA\\UserOIDC\\Vendor\\", + "dep_directory": "/lib/Vendor/", + "classmap_directory": "/lib/autoload/", + "classmap_prefix": "NEXTCLOUD_USER_OIDC_", + "packages": [ + "firebase/php-jwt", + "id4me/id4me-rp", + "spomky-labs/aes-key-wrap", + "web-token/jwt-core", + "web-token/jwt-signature", + "web-token/jwt-encryption" + ], + "delete_vendor_directories": true, + "override_autoload": { + "id4me/id4me-rp": { + "psr-4": { + "Id4me\\RP\\": "src/" + } + } + } }, - "bamarni-bin": { - "bin-links": true, - "target-directory": "vendor-bin", - "forward-command": true - } + "bamarni-bin": { + "bin-links": true, + "target-directory": "vendor-bin", + "forward-command": true + } } } diff --git a/composer.lock b/composer.lock index e41e91796..c4af10246 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": "d04dc4373433d9ac88a4fd120a014533", + "content-hash": "95d808e08a0169cc660bc75046c0dd31", "packages": [ { "name": "bamarni/composer-bin-plugin", @@ -63,6 +63,66 @@ }, "time": "2026-02-04T10:18:12+00:00" }, + { + "name": "brick/math", + "version": "0.12.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-02-28T13:11:00+00:00" + }, { "name": "firebase/php-jwt", "version": "v7.0.5", @@ -173,41 +233,1944 @@ "time": "2020-09-24T07:04:40+00:00" }, { - "name": "phpseclib/phpseclib", - "version": "2.0.53", + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2025-09-24T15:06:41+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/4714da6efdc782c06690bc72ce34fae7941c2d9f", + "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f", + "shasum": "" + }, + "require": { + "php": "^8.1", + "php-64bit": "*" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^7|^8|^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "suggest": { + "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "ParagonIE\\Sodium\\": "namespaced/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v2.5.0" + }, + "time": "2025-12-30T16:12:18+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "2.0.53", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2d1a664b940b9b8f367185307dc010d11a2790f3", + "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^8.5|^9.4", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.", + "ext-xml": "Install the XML extension to load XML formatted public keys." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.53" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2026-04-10T01:30:02+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "spomky-labs/aes-key-wrap", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/aes-key-wrap.git", + "reference": "fbeb834b1f83aa8fbdfbd4c12124f71d4c1606ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/aes-key-wrap/zipball/fbeb834b1f83aa8fbdfbd4c12124f71d4c1606ae", + "reference": "fbeb834b1f83aa8fbdfbd4c12124f71d4c1606ae", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-openssl": "*", + "php": ">=8.0" + }, + "require-dev": { + "infection/infection": "^0.25.4", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.0", + "rector/rector": "^0.12.5", + "symplify/easy-coding-standard": "^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AESKW\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/aes-key-wrap/contributors" + } + ], + "description": "AES Key Wrap for PHP.", + "homepage": "https://github.com/Spomky-Labs/aes-key-wrap", + "keywords": [ + "A128KW", + "A192KW", + "A256KW", + "RFC3394", + "RFC5649", + "aes", + "key", + "padding", + "wrap" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/aes-key-wrap/issues", + "source": "https://github.com/Spomky-Labs/aes-key-wrap/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2021-12-08T20:36:59+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/aa576cbd07128075bef97ac2f8af9854e67513d8", + "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8", + "shasum": "" + }, + "require": { + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17", + "ext-mbstring": "*", + "php": ">=8.1", + "psr/clock": "^1.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.28|^0.29|^0.31|^0.32", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.3|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0|^13.0", + "rector/rector": "^1.0|^2.0", + "roave/security-advisories": "dev-latest", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symplify/easy-coding-standard": "^12.0|^13.0" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" + }, + "type": "library", + "autoload": { + "psr-4": { + "SpomkyLabs\\Pki\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" + } + ], + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", + "keywords": [ + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.2" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-03-23T22:56:56+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-04-22T15:21:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", + "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-04-29T13:25:15+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-29T11:18:49+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-04-26T13:13:48+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-04-10T17:25:58+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-04-10T17:25:58+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "114ac57257d75df748eda23dd003878080b8e688" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-03-24T13:12:05+00:00" + }, + { + "name": "web-token/jwt-core", + "version": "3.4.8", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-core.git", + "reference": "0a47aa6096024af3bff8082e47e27219b9889542" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-core/zipball/0a47aa6096024af3bff8082e47e27219b9889542", + "reference": "0a47aa6096024af3bff8082e47e27219b9889542", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "ext-json": "*", + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.2.1", + "web-token/jwt-library": "^3.3" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-core/tree/3.4.8" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "abandoned": "web-token/jwt-library", + "time": "2024-06-24T16:31:57+00:00" + }, + { + "name": "web-token/jwt-encryption", + "version": "3.4.8", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-encryption.git", + "reference": "3e4b1c4080a77a6e97b62b33562805c1fc552b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-encryption/zipball/3e4b1c4080a77a6e97b62b33562805c1fc552b5e", + "reference": "3e4b1c4080a77a6e97b62b33562805c1fc552b5e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "web-token/jwt-library": "^3.3" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-encryption/tree/3.4.8" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "abandoned": "web-token/jwt-library", + "time": "2024-02-22T07:19:34+00:00" + }, + { + "name": "web-token/jwt-library", + "version": "3.4.9", "source": { "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3" + "url": "https://github.com/web-token/jwt-library.git", + "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2d1a664b940b9b8f367185307dc010d11a2790f3", - "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f", + "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "ext-json": "*", + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "paragonie/sodium_compat": "^1.20|^2.0", + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", + "psr/clock": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "spomky-labs/pki-framework": "^1.2.1", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.12" }, - "require-dev": { - "phing/phing": "~2.7", - "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^8.5|^9.4", - "squizlabs/php_codesniffer": "~2.0" + "conflict": { + "spomky-labs/jose": "*" }, "suggest": { - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.", - "ext-xml": "Install the XML extension to load XML formatted public keys." + "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", + "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", + "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", + "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW...)", + "symfony/http-client": "To enable JKU/X5U support." }, "type": "library", "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], "psr-4": { - "phpseclib\\": "phpseclib/" + "Jose\\Component\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -216,71 +2179,114 @@ ], "authors": [ { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" } ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", + "description": "JWT library", + "homepage": "https://github.com/web-token", "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" ], "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.53" + "issues": "https://github.com/web-token/jwt-library/issues", + "source": "https://github.com/web-token/jwt-library/tree/3.4.9" }, "funding": [ { - "url": "https://github.com/terrafrost", + "url": "https://github.com/Spomky", "type": "github" }, { - "url": "https://www.patreon.com/phpseclib", + "url": "https://www.patreon.com/FlorentMorselli", "type": "patreon" + } + ], + "time": "2025-11-17T20:20:37+00:00" + }, + { + "name": "web-token/jwt-signature", + "version": "3.4.8", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-signature.git", + "reference": "eccfd59e658d4118414cf6d14229aa52eec387e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/eccfd59e658d4118414cf6d14229aa52eec387e7", + "reference": "eccfd59e658d4118414cf6d14229aa52eec387e7", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "web-token/jwt-library": "^3.3" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" } ], - "time": "2026-04-10T01:30:02+00:00" + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-signature/tree/3.4.8" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "abandoned": "web-token/jwt-library", + "time": "2024-02-22T07:19:34+00:00" } ], "packages-dev": [ @@ -1337,107 +3343,6 @@ ], "time": "2026-02-18T12:37:06+00:00" }, - { - "name": "psr/clock", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/clock.git", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Clock\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for reading the clock.", - "homepage": "https://github.com/php-fig/clock", - "keywords": [ - "clock", - "now", - "psr", - "psr-20", - "time" - ], - "support": { - "issues": "https://github.com/php-fig/clock/issues", - "source": "https://github.com/php-fig/clock/tree/1.0.0" - }, - "time": "2022-11-25T14:36:26+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, { "name": "psr/event-dispatcher", "version": "1.0.0", @@ -1488,56 +3393,6 @@ }, "time": "2019-01-08T18:20:26+00:00" }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, { "name": "sebastian/cli-parser", "version": "3.0.2", diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 192220cab..693c9356f 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -21,11 +21,11 @@ use OCA\UserOIDC\Listener\InternalTokenRequestedListener; use OCA\UserOIDC\Listener\TimezoneHandlingListener; use OCA\UserOIDC\Listener\TokenInvalidatedListener; +use OCA\UserOIDC\MagentaBearer\MBackend; use OCA\UserOIDC\Service\ID4MeService; use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; -use OCA\UserOIDC\User\Backend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -33,9 +33,11 @@ use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; +use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; +use OCP\Security\ISecureRandom; use Throwable; class Application extends App implements IBootstrap { @@ -54,7 +56,7 @@ public function register(IRegistrationContext $context): void { $userManager = $this->getContainer()->get(IUserManager::class); /* Register our own user backend */ - $this->backend = $this->getContainer()->get(Backend::class); + $this->backend = $this->getContainer()->get(MBackend::class); $config = $this->getContainer()->get(IConfig::class); if (version_compare($config->getSystemValueString('version', '0.0.0'), '32.0.0', '>=')) { @@ -93,6 +95,7 @@ public function boot(IBootContext $context): void { try { $context->injectFn(\Closure::fromCallable([$this, 'registerRedirect'])); + $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow'])); if (version_compare($this->getContainer()->get(IConfig::class)->getSystemValueString('version', '0.0.0'), '34.0.0', '<')) { $context->injectFn(\Closure::fromCallable([$this, 'registerLogin'])); } @@ -158,6 +161,56 @@ private function registerLogin( } } + /** + * This is the automatic redirect exclusively for Nextcloud/Magentacloud clients, completely skipping consent layer. + */ + private function registerNmcClientFlow( + IRequest $request, + IURLGenerator $urlGenerator, + ProviderMapper $providerMapper, + ISession $session, + ISecureRandom $random, + ): void { + $providers = $this->getCachedProviders($providerMapper); + + try { + $isClientLoginFlow = $request->getPathInfo() === '/login/flow'; + } catch (Exception) { + return; + } + + if (!$isClientLoginFlow) { + return; + } + + $tproviders = array_values(array_filter($providers, static function ($provider): bool { + return strtolower($provider->getIdentifier()) === 'telekom'; + })); + + if (count($tproviders) === 0) { + return; + } + + $stateToken = $random->generate(64, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); + + $session->set('client.flow.state.token', $stateToken); + + $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [ + 'stateToken' => $stateToken, + 'clientIdentifier' => $request->getParam('clientIdentifier', ''), + 'direct' => $request->getParam('direct', '0'), + ]); + + $targetUrl = $urlGenerator->linkToRoute(self::APP_ID . '.login.login', [ + 'providerId' => $tproviders[0]->getId(), + 'redirectUrl' => $redirectUrl, + ]); + + header('Location: ' . $targetUrl); + + exit(); + } + private function getCachedProviders(ProviderMapper $providerMapper): array { if (!isset($this->cachedProviders)) { $this->cachedProviders = $providerMapper->getProviders(); diff --git a/lib/MagentaBearer/MBackend.php b/lib/MagentaBearer/MBackend.php new file mode 100644 index 000000000..96010d964 --- /dev/null +++ b/lib/MagentaBearer/MBackend.php @@ -0,0 +1,148 @@ +request->getHeader(Application::OIDC_API_REQ_HEADER); + + return preg_match('/^\s*bearer\s+/i', $headerToken) === 1; + } + + public function getCurrentUserId(): string { + $headerToken = $this->request->getHeader(Application::OIDC_API_REQ_HEADER); + + if (preg_match('/^\s*bearer\s+/i', $headerToken) !== 1) { + $this->logger->debug('No Bearer token'); + return ''; + } + + $headerToken = preg_replace('/^\s*bearer\s+/i', '', $headerToken); + if (!is_string($headerToken) || $headerToken === '') { + $this->logger->debug('No Bearer token'); + return ''; + } + + $providers = $this->providerMapper->getProviders(); + if (count($providers) === 0) { + $this->logger->debug('No OIDC providers'); + return ''; + } + + foreach ($providers as $provider) { + if ($this->providerService->getSetting($provider->getId(), ProviderService::SETTING_CHECK_BEARER, '0') !== '1') { + continue; + } + + try { + $sharedSecret = $this->crypto->decrypt($provider->getBearerSecret()); + $bearerToken = $this->mtokenService->decryptToken($headerToken, $sharedSecret); + $this->mtokenService->verifySignature($bearerToken, $sharedSecret); + + $payload = $this->mtokenService->decode($bearerToken); + $this->mtokenService->verifyClaims($payload, ['http://auth.magentacloud.de']); + } catch (InvalidTokenException $e) { + $this->logger->debug('Invalid token: ' . $e->getMessage() . '. Trying another provider.'); + continue; + } catch (SignatureException $e) { + $this->logger->debug($e->getMessage() . '. Trying another provider.'); + continue; + } catch (\Throwable $e) { + $this->logger->debug('General non-matching provider problem: ' . $e->getMessage()); + continue; + } + + $uidAttribute = $this->providerService->getSetting($provider->getId(), ProviderService::SETTING_MAPPING_UID, 'sub'); + $userId = is_object($payload) ? ($payload->{$uidAttribute} ?? null) : null; + + if (!$this->isAcceptableUserId($userId)) { + $this->logger->debug('No extractable user id, check mapping!'); + return ''; + } + + try { + $provisioningResult = $this->provisioningService->provisionUser($userId, $provider->getId(), $payload); + $provisionedUser = $provisioningResult['user'] ?? null; + + if ($provisionedUser instanceof IUser) { + $userId = $provisionedUser->getUID(); + } + + $this->checkFirstLogin($userId); + + return $userId; + } catch (ProvisioningDeniedException $e) { + $this->logger->error('Bearer token access denied: ' . $e->getMessage()); + return ''; + } + } + + $this->logger->debug('Could not find provider for token'); + + return ''; + } +} diff --git a/lib/User/AbstractOidcBackend.php b/lib/User/AbstractOidcBackend.php new file mode 100644 index 000000000..dad8e9614 --- /dev/null +++ b/lib/User/AbstractOidcBackend.php @@ -0,0 +1,192 @@ +userMapper->countUsers(); + + if ($limit > 0 && $count > $limit) { + return $limit; + } + + return $count; + } catch (\Throwable $e) { + $this->logger->error('Failed to count OIDC users', [ + 'exception' => $e, + ]); + + return false; + } + } + + public function deleteUser($uid): bool { + if (!is_string($uid) || $uid === '') { + return false; + } + + try { + $user = $this->userMapper->getUser($uid); + $this->userMapper->delete($user); + return true; + } catch (DoesNotExistException $e) { + $this->logger->info('Tried to delete non-existent user', [ + 'uid' => $uid, + 'exception' => $e, + ]); + return false; + } catch (Exception $e) { + $this->logger->error('Failed to delete user', [ + 'uid' => $uid, + 'exception' => $e, + ]); + return false; + } + } + + public function getUsers($search = '', $limit = null, $offset = null): array { + if (!is_string($search) + || ($limit !== null && !is_int($limit)) + || ($offset !== null && !is_int($offset)) + ) { + return []; + } + + return array_map( + static fn ($user) => $user->getUserId(), + $this->userMapper->find($search, $limit, $offset) + ); + } + + public function userExists($uid): bool { + return is_string($uid) && $uid !== '' && $this->userMapper->userExists($uid); + } + + public function getDisplayName($uid): string { + if (!is_string($uid) || $uid === '') { + return (string)$uid; + } + + try { + $user = $this->userMapper->getUser($uid); + return $user->getDisplayName(); + } catch (DoesNotExistException) { + return $uid; + } + } + + public function getDisplayNames($search = '', $limit = null, $offset = null): array { + if (!is_string($search) + || ($limit !== null && !is_int($limit)) + || ($offset !== null && !is_int($offset)) + ) { + return []; + } + + return $this->userMapper->findDisplayNames($search, $limit, $offset); + } + + public function hasUserListings(): bool { + return true; + } + + public function canConfirmPassword(string $uid): bool { + return false; + } + + public function injectSession(ISession $session): void { + $this->session = $session; + } + + public function getLogoutUrl(): string { + return $this->urlGenerator->linkToRouteAbsolute('user_oidc.login.singleLogoutService'); + } + + protected function isAcceptableUserId(mixed $userId): bool { + return is_string($userId) && trim($userId) !== ''; + } + + protected function checkFirstLogin(string $userId): bool { + $user = $this->userManager->get($userId); + if ($user === null) { + return false; + } + + $firstLogin = $user->getLastLogin() === 0; + + if ($firstLogin) { + try { + if (version_compare($this->config->getSystemValueString('version', '0.0.0'), '34.0.0', '>=') + && interface_exists(ISetupManager::class) + ) { + Server::get(ISetupManager::class)->setupForUser($user); + } else { + \OC_Util::setupFS($userId); + } + + $userFolder = Server::get(IRootFolder::class)->getUserFolder($userId); + \OC_Util::copySkeleton($userId, $userFolder); + } catch (\Throwable $e) { + $this->logger->warning('Could not fully set up user filesystem on first login', [ + 'userId' => $userId, + 'exception' => $e, + ]); + } + + if (class_exists(UserFirstTimeLoggedInEvent::class)) { + $this->eventDispatcher->dispatchTyped(new UserFirstTimeLoggedInEvent($user)); + } + } + + $user->updateLastLoginTimestamp(); + + return $firstLogin; + } +} From bf786a4d5740f7705aaeffcb1108dc8e42993814 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 13:15:00 +0200 Subject: [PATCH 32/57] disable endpoints --- appinfo/routes.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index faf0ae16b..d357f42cf 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -31,8 +31,9 @@ ['name' => 'Settings#setID4ME', 'url' => '/api/{apiVersion}/provider/id4me', 'verb' => 'POST', 'requirements' => $requirements], ['name' => 'Settings#getSupportedSettings', 'url' => '/api/{apiVersion}/supported-settings', 'verb' => 'GET', 'requirements' => $requirements], ['name' => 'Settings#setAdminConfig', 'url' => '/api/{apiVersion}/admin-config', 'verb' => 'POST', 'requirements' => $requirements], - - ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], - ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], + + // We have to disable the endpoints to avoid problems with Telekom provisioning + // ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], + // ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], ], ]; From 0502655b55d3fb3debd12807dd3c87db200ecf65 Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Wed, 6 May 2026 13:29:48 +0200 Subject: [PATCH 33/57] Comment out checkLoginToken injection in boot method Comment out the injection of the checkLoginToken function in the boot method. --- lib/AppInfo/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 693c9356f..4f3a0e942 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -86,7 +86,7 @@ public function register(IRegistrationContext $context): void { public function boot(IBootContext $context): void { $context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession'])); - $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken'])); + // $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken'])); /** @var IUserSession $userSession */ $userSession = $this->getContainer()->get(IUserSession::class); if ($userSession->isLoggedIn()) { From 196f985a3c1d427bfbe965eb806acb3b02c801b0 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 16:40:07 +0200 Subject: [PATCH 34/57] added event based provisioning --- lib/AppInfo/Application.php | 8 + lib/Controller/LoginController.php | 16 +- lib/Event/UserAccountChangeEvent.php | 60 ++ lib/Event/UserAccountChangeResult.php | 46 ++ lib/Service/ProvisioningDeniedException.php | 31 + lib/Service/ProvisioningEventService.php | 186 ++++++ .../unit/MagentaCloud/OpenidTokenTestCase.php | 122 ++++ .../ProvisioningEventServiceTest.php | 613 ++++++++++++++++++ tests/unit/MagentaCloud/RegistrationsTest.php | 33 + 9 files changed, 1114 insertions(+), 1 deletion(-) create mode 100644 lib/Event/UserAccountChangeEvent.php create mode 100644 lib/Event/UserAccountChangeResult.php create mode 100644 lib/Service/ProvisioningDeniedException.php create mode 100644 lib/Service/ProvisioningEventService.php create mode 100644 tests/unit/MagentaCloud/OpenidTokenTestCase.php create mode 100644 tests/unit/MagentaCloud/ProvisioningEventServiceTest.php create mode 100644 tests/unit/MagentaCloud/RegistrationsTest.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 192220cab..6958a6c4d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -22,6 +22,8 @@ use OCA\UserOIDC\Listener\TimezoneHandlingListener; use OCA\UserOIDC\Listener\TokenInvalidatedListener; use OCA\UserOIDC\Service\ID4MeService; +use OCA\UserOIDC\Service\ProvisioningEventService; +use OCA\UserOIDC\Service\ProvisioningService; use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; @@ -36,6 +38,7 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; +use Psr\Container\ContainerInterface; use Throwable; class Application extends App implements IBootstrap { @@ -50,6 +53,11 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { + // override registration of provisioning service to use event-based solution + $this->getContainer()->registerService(ProvisioningService::class, function (ContainerInterface $c): ProvisioningService { + return $c->get(ProvisioningEventService::class); + }); + /** @var IUserManager $userManager */ $userManager = $this->getContainer()->get(IUserManager::class); diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php index abba810cc..44945864e 100644 --- a/lib/Controller/LoginController.php +++ b/lib/Controller/LoginController.php @@ -24,6 +24,7 @@ use OCA\UserOIDC\Service\LdapService; use OCA\UserOIDC\Service\OIDCService; use OCA\UserOIDC\Service\ProviderService; +use OCA\UserOIDC\Service\ProvisioningDeniedException; use OCA\UserOIDC\Service\ProvisioningService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; @@ -654,8 +655,21 @@ public function code(string $state = '', string $code = '', string $scope = '', $message = $this->l10n->t('User conflict'); return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => 'non-soft auto provision, user conflict'], false); } + // use potential user from other backend, create it in our backend if it does not exist - $provisioningResult = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser); + try { + $provisioningResult = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser); + } catch (ProvisioningDeniedException $denied) { + $redirectUrl = $denied->getRedirectUrl(); + + if ($redirectUrl === null) { + $message = $this->l10n->t('Failed to provision user'); + return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => $denied->getMessage()]); + } + + return new RedirectResponse($redirectUrl); + } + $user = $provisioningResult['user']; if ($existingUser === null && $user !== null) { // we know we just created a user diff --git a/lib/Event/UserAccountChangeEvent.php b/lib/Event/UserAccountChangeEvent.php new file mode 100644 index 000000000..94a2064d0 --- /dev/null +++ b/lib/Event/UserAccountChangeEvent.php @@ -0,0 +1,60 @@ +result = new UserAccountChangeResult($accessAllowed, 'default'); + } + + public function getUid(): string { + return $this->uid; + } + + public function getDisplayName(): ?string { + return $this->displayName; + } + + public function getMainEmail(): ?string { + return $this->mainEmail; + } + + public function getQuota(): ?string { + return $this->quota; + } + + public function getClaims(): object { + return $this->claims; + } + + public function getResult(): UserAccountChangeResult { + return $this->result; + } + + public function setResult(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null): void { + $this->result = new UserAccountChangeResult($accessAllowed, $reason, $redirectUrl); + } +} diff --git a/lib/Event/UserAccountChangeResult.php b/lib/Event/UserAccountChangeResult.php new file mode 100644 index 000000000..aace323ab --- /dev/null +++ b/lib/Event/UserAccountChangeResult.php @@ -0,0 +1,46 @@ +accessAllowed; + } + + public function setAccessAllowed(bool $accessAllowed): void { + $this->accessAllowed = $accessAllowed; + } + + public function getReason(): string { + return $this->reason; + } + + public function setReason(string $reason): void { + $this->reason = $reason; + } + + public function getRedirectUrl(): ?string { + return $this->redirectUrl; + } + + public function setRedirectUrl(?string $redirectUrl): void { + $this->redirectUrl = $redirectUrl; + } +} diff --git a/lib/Service/ProvisioningDeniedException.php b/lib/Service/ProvisioningDeniedException.php new file mode 100644 index 000000000..35cc4ac60 --- /dev/null +++ b/lib/Service/ProvisioningDeniedException.php @@ -0,0 +1,31 @@ +redirectUrl; + } + + public function __toString(): string { + $redirect = $this->redirectUrl ?? ''; + + return self::class . ": [{$this->code}]: {$this->message} ({$redirect})\n"; + } +} diff --git a/lib/Service/ProvisioningEventService.php b/lib/Service/ProvisioningEventService.php new file mode 100644 index 000000000..4fe22ed3e --- /dev/null +++ b/lib/Service/ProvisioningEventService.php @@ -0,0 +1,186 @@ +providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_UID, 'sub'); + $mappedUserId = $payload->{$uidAttribute} ?? $tokenUserId; + + if (!is_string($mappedUserId) || trim($mappedUserId) === '') { + throw new AttributeValueException('Mapped uid is empty or invalid'); + } + + $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_UID, $payload, $mappedUserId); + $this->eventDispatcher->dispatchTyped($event); + + $value = $event->getValue(); + if (!is_string($value) || trim($value) === '') { + throw new AttributeValueException('Mapped uid is empty or invalid'); + } + + return $value; + } + + protected function mapDispatchDisplayname(int $providerId, object $payload): ?string { + $displaynameAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_DISPLAYNAME, 'displayname'); + $mappedDisplayName = $payload->{$displaynameAttribute} ?? null; + + if (is_string($mappedDisplayName) && $mappedDisplayName !== '') { + $mappedDisplayName = mb_substr($mappedDisplayName, 0, 255); + } elseif ($mappedDisplayName !== null) { + $mappedDisplayName = (string)$mappedDisplayName; + } + + $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_DISPLAYNAME, $payload, $mappedDisplayName); + $this->eventDispatcher->dispatchTyped($event); + + $value = $event->getValue(); + + return $value === null ? null : (string)$value; + } + + protected function mapDispatchEmail(int $providerId, object $payload): ?string { + $emailAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_EMAIL, 'email'); + $mappedEmail = $payload->{$emailAttribute} ?? null; + + $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_EMAIL, $payload, $mappedEmail); + $this->eventDispatcher->dispatchTyped($event); + + $value = $event->getValue(); + + return $value === null ? null : (string)$value; + } + + protected function mapDispatchQuota(int $providerId, object $payload): ?string { + $quotaAttribute = $this->providerService->getSetting($providerId, ProviderService::SETTING_MAPPING_QUOTA, 'quota'); + $mappedQuota = $payload->{$quotaAttribute} ?? null; + + $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_QUOTA, $payload, $mappedQuota); + $this->eventDispatcher->dispatchTyped($event); + + $value = $event->getValue(); + + return $value === null ? null : (string)$value; + } + + protected function dispatchUserAccountUpdate( + string $uid, + ?string $displayName, + ?string $email, + ?string $quota, + object $payload, + ): mixed { + $event = new UserAccountChangeEvent($uid, $displayName, $email, $quota, $payload); + $this->eventDispatcher->dispatchTyped($event); + + return $event->getResult(); + } + + /** + * Trigger provisioning via event system. + * + * @return array{user: ?IUser, userData: array} + * @throws Exception + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ProvisioningDeniedException + */ + public function provisionUser( + string $tokenUserId, + int $providerId, + object $idTokenPayload, + ?IUser $existingLocalUser = null, + ): array { + try { + $uid = $this->mapDispatchUID($providerId, $idTokenPayload, $tokenUserId); + $displayName = $this->mapDispatchDisplayname($providerId, $idTokenPayload); + $email = $this->mapDispatchEmail($providerId, $idTokenPayload); + $quota = $this->mapDispatchQuota($providerId, $idTokenPayload); + } catch (AttributeValueException $e) { + $this->logger->info($tokenUserId . ': user rejected by OpenID web authorization, reason: ' . $e->getMessage()); + throw new ProvisioningDeniedException($e->getMessage()); + } + + $userReaction = $this->dispatchUserAccountUpdate($uid, $displayName, $email, $quota, $idTokenPayload); + + if ($userReaction->isAccessAllowed()) { + $this->logger->info($uid . ': account accepted, reason: ' . $userReaction->getReason()); + + return [ + 'user' => $existingLocalUser ?? $this->userManager->get($uid), + 'userData' => get_object_vars($idTokenPayload), + ]; + } + + $this->logger->info($uid . ': account rejected, reason: ' . $userReaction->getReason()); + + throw new ProvisioningDeniedException( + $userReaction->getReason(), + $userReaction->getRedirectUrl(), + ); + } +} diff --git a/tests/unit/MagentaCloud/OpenidTokenTestCase.php b/tests/unit/MagentaCloud/OpenidTokenTestCase.php new file mode 100644 index 000000000..ecc648c21 --- /dev/null +++ b/tests/unit/MagentaCloud/OpenidTokenTestCase.php @@ -0,0 +1,122 @@ + */ + private array $realOidClaims = []; + + public function getProviderId(): int { + return 4711; + } + + /** @return array */ + public function getRealOidClaims(): array { + return $this->realOidClaims; + } + + public function getOidClientId(): string { + return 'USER_NC_OPENID_TEST'; + } + + public function getOidNonce(): string { + return 'CVMI8I3JZPALSL5DIM6I1PDP8SDSEN4K'; + } + + public function getOidClientSecret(): string { + return 'JQ17C99A-DAF8-4E27-FBW4-GV23B043C993'; + } + + public function getOidServerKey(): string { + return Base64UrlSafe::encodeUnpadded('JQ17DAF8-C99A-4E27-FBW4-GV23B043C993'); + } + + /** @return array */ + public function getOidPrivateServerKey(): array { + return [ + 'p' => '9US9kD6Q8nicR1se1U_iRI9x1iK0__HF7E9yhqrza9DHldC2h7PLuR7y9bITAUtcBmVvqEQlVUXRZPMrNUpLFI9hTdZXAACRqYBYGHg7Mvyzq-2JXhEE5CFDy9wSCPunc8bRq4TsY0ocSXugXKGjx-t1uO3fkF1UgNgNMjdzSPM', + 'kty' => 'RSA', + 'q' => '85auJF6W3c91EebGpjMX-g_U0fLBMgO2oxBsldus9x2diRd3wVvUnrTg5fQctODdr4if8dBCPDdLxBUKul4MXULC_nCkGkDjORdESb7j8amGnOvxnaVcQT6C5yHivAawa4R8NchR7n23VrQWO8fHhQBYUHTTy01G3A8D6dznCC8', + 'd' => 'tP-lT4FJBKrhhBUk7J1fR0638jVjn46yIfSaB5l_JlqNItmRJtbz3QWopy4oDfvrY_ccZIYG9tLvJH-3LHtuEddwxFsL-9MSUx5qxWB4sKpKA6EpxWNR5EFnFKxR_B2P2yFYiRDdbBh8h9pNaOuNjZU5iitAGvSOfW4X5hyJyu9t9zsEX9O6stEtP3yK5sx-bt7osGDMIguFBMcPVHbYw_Pl7-aNPuQ4ioxVXa3JlO6tTcDrcyMy7d3CWuGACj3juEnO-1n8E_OSR9sMp1k_L7i-qQ3OnLCOx07HeTWklCvNxz7U9qLcQXGcfpdWmhWZt6MO3SIXV4f6Md0U836v0Q', + 'e' => 'AQAB', + 'use' => 'sig', + 'kid' => '0123456789', + 'qi' => 'T3-NLCpVoITdS6PB9XYCsXsfhQSiE_4eTQnZf_Zya5hSd0xZDrrwNiXL8Dzy3YLjsZAFC0U6wAeC2wTBJ8c-6VxdP34J0sGj2I_TNhFFArksLy9ZaRbskCxKAPLipEFi8b1H2-aaRFRLs6BQJbfesQ5mcX2kB5AItAX3R6tcc0A', + 'dp' => 'ExUtFor3phXiOt8JEBmuBh2PAtUidgNuncs0ouusEshkrvBVM0u23wlcZ-dZ-TDO0SSVQmdC7FaJSyxsQTItk0TwkijKDhL9Qk3dDNJV8MqehBLwLCRw1_sKllLiCFbkGWrvp0OpTLRYbRM0T-C3qHdWanP_f_DzAS9OH4kW7Cc', + 'alg' => 'RS256', + 'dq' => 'xr3XAWeHkhw0uVFgHLQtSOJn0pBM3qC2_95jqfVc7xZjtTnHhKSHGqIbqKL-VPnvBcvkK-iuUfEPyUEdyqb3UZQqAm0nByCQA8Ge_shXtJGLejbroKMNXVJCfZBhLOYMRP0IVt1FM9-wmXY_ebDrcfGxHJvlPcekG-HIYKPSgBM', + 'n' => '6WCdDo8KuksEFaFlzvmsaoYhfOoMt5XgnX98dx-F1OUz53SG0lQlFt-xkwra5B4GZ-13lki0qCa2CjA1aLa9kEvDdYhz_0Uc5qOy5haDj8Jn547s6gFyaLzJ0RN5i5eKeDMHcjeEC0_NjiB2UNUFJJ61b2nXIlUvp_vBfKCv4A-8C3mLSbCKJQhX84QRDgt_Abz0MXj_ga72Ka2cwVLo4OFQAK5m57Qfu9ZvseMcgoinyhIQ18b98SkWinn3DM0W1KXLkWLk0S3XEMxLV1M7-9RLo4fgEGOpX1xmmM6KbsC5SxXvRUO7tjU-o35fcewDwXYHnRbxqhRkEFfWb7b8nQ', + ]; + } + + public function getOidPublicServerKey(): array { + return [ + '0123456789' => new \OCA\UserOIDC\Vendor\Firebase\JWT\Key( + $this->getOidClientSecret(), + 'HS256', + ), + ]; + } + + public function getOidTestCode(): string { + return '66844608'; + } + + public function getOidTestState(): string { + return '4VSL5T274MJEMLZI1810HUFDA07CEPXZ'; + } + + public function setUp(): void { + parent::setUp(); + + $this->app = new App(Application::APP_ID); + $now = time(); + + $this->realOidClaims = [ + 'sub' => 'jgyros', + 'urn:custom.com:displayname' => 'Jonny G', + 'urn:custom.com:email' => 'jonny.gyros@x.y', + 'urn:custom.com:mainEmail' => 'jonny.gyuris@x.y.de', + 'iss' => 'https://accounts.login00.custom.de', + 'urn:custom.com:feat1' => '0', + 'urn:custom.com:uid' => '081500000001234', + 'urn:custom.com:feat2' => '1', + 'urn:custom.com:ext2' => '0', + 'urn:custom.com:feat3' => '1', + 'acr' => 'urn:custom:names:idm:THO:1.0:ac:classes:passid:00', + 'urn:custom.com:feat4' => '0', + 'urn:custom.com:ext4' => '0', + 'auth_time' => $now, + 'exp' => $now + 7200, + 'iat' => $now, + 'urn:custom.com:session_token' => 'ad0fff71-e013-11ec-9e17-39677d2c891c', + 'nonce' => $this->getOidNonce(), + 'aud' => [$this->getOidClientId()], + ]; + } + + protected function createSignToken(array $claims): string { + return \OCA\UserOIDC\Vendor\Firebase\JWT\JWT::encode( + $claims, + $this->getOidClientSecret(), + 'HS256', + '0123456789', + ); + } +} diff --git a/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php b/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php new file mode 100644 index 000000000..2a413e1c8 --- /dev/null +++ b/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php @@ -0,0 +1,613 @@ +createMock(IConfig::class); + + $config->expects($this->any()) + ->method('getSystemValue') + ->with($this->logicalOr($this->equalTo('user_oidc'), $this->equalTo('secret'))) + ->willReturnCallback(static function (string $key, mixed $default = null): mixed { + if ($key === 'user_oidc') { + return [ + 'auto_provisioning' => true, + 'auto_provision' => true, + 'soft_auto_provision' => true, + 'login_validation_audience_check' => false, + 'login_validation_azp_check' => false, + ]; + } + + if ($key === 'secret') { + return 'Streng_geheim'; + } + + return $default; + }); + + $config->expects($this->any()) + ->method('getSystemValueString') + ->willReturnCallback(static function (string $key, string $default = ''): string { + if ($key === 'version') { + return '32.0.0'; + } + + return $default; + }); + + $config->expects($this->any()) + ->method('setUserValue'); + + return $config; + } + + protected function getOidSessionSetup(): MockObject { + $session = $this->createMock(ISession::class); + + $session->expects($this->any()) + ->method('get') + ->willReturnCallback(function (string $key): mixed { + $state = $this->getOidTestState(); + $suffix = '-' . $state; + + $values = [ + 'oidc.state' . $suffix => $state, + 'oidc.login.providerid' . $suffix => $this->getProviderId(), + 'oidc.providerid' . $suffix => $this->getProviderId(), + 'oidc.nonce' . $suffix => $this->getOidNonce(), + 'oidc.redirect' . $suffix => 'https://welcome.to.magenta', + 'oidc.timestamp' . $suffix => time(), + 'oidc.code_verifier' . $suffix => 'test-code-verifier', + ]; + + return $values[$key] ?? null; + }); + + $session->expects($this->any()) + ->method('exists') + ->willReturnCallback(function (string $key): bool { + $state = $this->getOidTestState(); + $suffix = '-' . $state; + + return in_array($key, [ + 'oidc.state' . $suffix, + 'oidc.login.providerid' . $suffix, + 'oidc.providerid' . $suffix, + 'oidc.nonce' . $suffix, + 'oidc.redirect' . $suffix, + 'oidc.timestamp' . $suffix, + 'oidc.code_verifier' . $suffix, + ], true); + }); + + $session->expects($this->any()) + ->method('set'); + + $session->expects($this->any()) + ->method('remove'); + + $session->expects($this->any()) + ->method('getId') + ->willReturn('test-session-id'); + + return $session; + } + + protected function getProviderSetup(): Provider { + $provider = new Provider(); + $provider->setId($this->getProviderId()); + $provider->setIdentifier('telekom'); + $provider->setClientId($this->getOidClientId()); + $provider->setClientSecret($this->crypto->encrypt($this->getOidClientSecret())); + $provider->setScope('openid'); + $provider->setDiscoveryEndpoint('https://accounts.login00.custom.de/.well-known/openid-configuration'); + + $this->providerMapper->expects($this->any()) + ->method('getProvider') + ->with($this->equalTo($this->getProviderId())) + ->willReturn($provider); + + return $provider; + } + + protected function getProviderServiceSetup(): MockObject { + $providerService = $this->getMockBuilder(ProviderService::class) + ->setConstructorArgs([$this->appConfig, $this->providerMapper]) + ->getMock(); + + $providerService->expects($this->any()) + ->method('getSetting') + ->willReturnCallback(static function (int $providerId, string $key, string $default = ''): string { + $values = [ + ProviderService::SETTING_MAPPING_UID => 'sub', + ProviderService::SETTING_MAPPING_DISPLAYNAME => 'urn:custom.com:displayname', + ProviderService::SETTING_MAPPING_QUOTA => 'urn:custom.com:f556', + ProviderService::SETTING_MAPPING_EMAIL => 'urn:custom.com:mainEmail', + ProviderService::SETTING_MAPPING_GROUPS => '', + ProviderService::SETTING_RESTRICT_LOGIN_TO_GROUPS => '0', + ProviderService::SETTING_RESOLVE_NESTED_AND_FALLBACK_CLAIMS_MAPPING => '0', + ProviderService::SETTING_EXTRA_CLAIMS => '', + ]; + + return $values[$key] ?? $default; + }); + + return $providerService; + } + + protected function getUserManagerSetup(): MockObject { + $userManager = $this->getMockForAbstractClass(IUserManager::class); + + $this->user = $this->getMockForAbstractClass(IUser::class); + $this->user->expects($this->any()) + ->method('canChangeAvatar') + ->willReturn(false); + $this->user->expects($this->any()) + ->method('getUID') + ->willReturn('jgyros'); + + return $userManager; + } + + public function setUp(): void { + parent::setUp(); + + $this->app = new App(Application::APP_ID); + $this->config = $this->getConfigSetup(); + $this->appConfig = $this->createMock(IAppConfig::class); + + $this->appConfig->expects($this->any()) + ->method('getValueString') + ->willReturn('0'); + + $this->appConfig->expects($this->any()) + ->method('getValueBool') + ->willReturn(false); + + $this->crypto = $this->getMockBuilder(Crypto::class) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $this->request = $this->getMockForAbstractClass(IRequest::class); + $this->request->expects($this->any()) + ->method('getServerProtocol') + ->willReturn('https'); + + $this->providerMapper = $this->getMockBuilder(ProviderMapper::class) + ->setConstructorArgs([$this->getMockForAbstractClass(IDBConnection::class)]) + ->getMock(); + + $this->provider = $this->getProviderSetup(); + $this->providerService = $this->getProviderServiceSetup(); + + $this->localIdService = $this->getMockBuilder(LocalIdService::class) + ->setConstructorArgs([ + $this->providerService, + $this->providerMapper, + ]) + ->getMock(); + + $this->userMapper = $this->getMockBuilder(UserMapper::class) + ->setConstructorArgs([ + $this->getMockForAbstractClass(IDBConnection::class), + $this->localIdService, + $this->config, + ]) + ->getMock(); + + $this->token = [ + 'id_token' => $this->createSignToken($this->getRealOidClaims()), + ]; + + $this->httpClientHelper = $this->getMockBuilder(HttpClientHelper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientHelper->expects($this->any()) + ->method('post') + ->willReturn(json_encode($this->token, JSON_THROW_ON_ERROR)); + + $this->discoveryService = $this->getMockBuilder(DiscoveryService::class) + ->setConstructorArgs([ + $this->app->getContainer()->get(LoggerInterface::class), + $this->httpClientHelper, + $this->providerService, + $this->app->getContainer()->get(IConfig::class), + $this->app->getContainer()->get(ITimeFactory::class), + $this->app->getContainer()->get(ICacheFactory::class), + ]) + ->getMock(); + + $this->discoveryService->expects($this->any()) + ->method('obtainDiscovery') + ->willReturn([ + 'token_endpoint' => 'https://whatever.to.discover/token', + 'authorization_endpoint' => 'https://whatever.to.discover/auth', + 'issuer' => 'https://accounts.login00.custom.de', + ]); + + $this->discoveryService->expects($this->any()) + ->method('obtainJWK') + ->willReturn($this->getOidPublicServerKey()); + + $this->session = $this->getOidSessionSetup(); + + $this->sessionMapper = $this->getMockBuilder(SessionMapper::class) + ->setConstructorArgs([ + $this->createMock(IDBConnection::class), + $this->app->getContainer()->get(ICrypto::class), + ]) + ->getMock(); + + $this->sessionMapper->expects($this->any()) + ->method('createOrUpdateSession'); + + $this->usersession = $this->getMockBuilder(IUserSession::class) + ->disableOriginalConstructor() + ->onlyMethods([ + 'setUser', + 'login', + 'logout', + 'getUser', + 'isLoggedIn', + 'getImpersonatingUserID', + 'setImpersonatingUserID', + 'setVolatileActiveUser', + ]) + ->addMethods([ + 'completeLogin', + 'createSessionToken', + 'createRememberMeToken', + ]) + ->getMock(); + + $this->usersession->expects($this->any()) + ->method('isLoggedIn') + ->willReturn(false); + + $this->usermanager = $this->getUserManagerSetup(); + $this->groupmanager = $this->getMockForAbstractClass(IGroupManager::class); + $this->dispatcher = $this->app->getContainer()->get(IEventDispatcher::class); + $this->l10nFactory = $this->app->getContainer()->get(IFactory::class); + + $this->provisioningService = new ProvisioningEventService( + $this->app->getContainer()->get(LocalIdService::class), + $this->providerService, + $this->userMapper, + $this->usermanager, + $this->groupmanager, + $this->dispatcher, + $this->app->getContainer()->get(LoggerInterface::class), + $this->app->getContainer()->get(IAccountManager::class), + $this->app->getContainer()->get(IClientService::class), + $this->app->getContainer()->get(IAvatarManager::class), + $this->config, + $this->session, + $this->l10nFactory, + $this->providerMapper, + $this->crypto, + ); + + $this->registrationContext = $this->app->getContainer() + ->get(Coordinator::class) + ->getRegistrationContext(); + + $this->settingsService = $this->getMockBuilder(SettingsService::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->settingsService->expects($this->any()) + ->method('getAllowMultipleUserBackEnds') + ->willReturn(true); + + $this->tokenService = $this->app->getContainer()->get(TokenService::class); + $this->oidcService = $this->app->getContainer()->get(OIDCService::class); + + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->timeFactory->expects($this->any()) + ->method('getTime') + ->willReturn(time()); + + $this->loginController = new LoginController( + $this->request, + $this->providerMapper, + $this->providerService, + $this->discoveryService, + $this->app->getContainer()->get(LdapService::class), + $this->settingsService, + $this->app->getContainer()->get(ISecureRandom::class), + $this->session, + $this->httpClientHelper, + $this->app->getContainer()->get(IURLGenerator::class), + $this->usersession, + $this->usermanager, + $this->timeFactory, + $this->dispatcher, + $this->config, + $this->appConfig, + $this->app->getContainer()->get(IProvider::class), + $this->sessionMapper, + $this->provisioningService, + $this->app->getContainer()->get(IL10N::class), + $this->app->getContainer()->get(LoggerInterface::class), + $this->crypto, + $this->tokenService, + $this->oidcService, + ); + + $this->attributeListener = null; + $this->accountListener = null; + } + + public function tearDown(): void { + if ($this->accountListener !== null) { + $this->dispatcher->removeListener(UserAccountChangeEvent::class, $this->accountListener); + } + + if ($this->attributeListener !== null) { + $this->dispatcher->removeListener(AttributeMappedEvent::class, $this->attributeListener); + } + + parent::tearDown(); + } + + protected function mockAssertLoginSuccess(): void { + $this->usermanager->expects($this->once()) + ->method('get') + ->willReturn($this->user); + + $this->usersession->expects($this->once()) + ->method('setUser') + ->with($this->equalTo($this->user)); + + $this->usersession->expects($this->any()) + ->method('completeLogin') + ->with($this->anything(), $this->anything()); + + $this->usersession->expects($this->any()) + ->method('createSessionToken'); + + $this->usersession->expects($this->any()) + ->method('createRememberMeToken'); + } + + protected function assertLoginRedirect(mixed $result): void { + if ($result instanceof TemplateResponse) { + $this->fail( + 'Expected RedirectResponse, got TemplateResponse. Template: ' + . $result->getTemplateName() + . ' Params: ' + . json_encode($result->getParams(), JSON_THROW_ON_ERROR) + ); + } + + $this->assertInstanceOf(RedirectResponse::class, $result); + } + + protected function assertLogin403(mixed $result): void { + $this->assertInstanceOf( + TemplateResponse::class, + $result, + 'LoginController->code() did not end with 403 Forbidden' + ); + } + + public function testNoMap_AccessOk(): void { + $this->mockAssertLoginSuccess(); + + $this->accountListener = function (Event $event): void { + $this->assertInstanceOf(UserAccountChangeEvent::class, $event); + $this->assertEquals('jgyros', $event->getUid()); + $this->assertEquals('Jonny G', $event->getDisplayName()); + $this->assertEquals('jonny.gyuris@x.y.de', $event->getMainEmail()); + $this->assertNull($event->getQuota()); + + $event->setResult(true, 'ok', null); + }; + + $this->dispatcher->addListener(UserAccountChangeEvent::class, $this->accountListener); + + $result = $this->loginController->code($this->getOidTestState(), $this->getOidTestCode(), ''); + + $this->assertLoginRedirect($result); + $this->assertNotEmpty($result->getRedirectURL()); + } + + public function testUidNoMapEvent_AccessOk(): void { + $this->mockAssertLoginSuccess(); + + $this->accountListener = function (Event $event): void { + $this->assertInstanceOf(UserAccountChangeEvent::class, $event); + $this->assertEquals('jgyros', $event->getUid()); + $this->assertEquals('Jonny G', $event->getDisplayName()); + $this->assertEquals('jonny.gyuris@x.y.de', $event->getMainEmail()); + $this->assertNull($event->getQuota()); + + $event->setResult(true, 'ok', 'https://welcome.to.darkside'); + }; + + $this->dispatcher->addListener(UserAccountChangeEvent::class, $this->accountListener); + + $result = $this->loginController->code($this->getOidTestState(), $this->getOidTestCode(), ''); + + $this->assertLoginRedirect($result); + $this->assertEquals('http://localhost', $result->getRedirectURL()); + } + + public function testDisplaynameMapEvent_NOk_NoRedirect(): void { + $this->attributeListener = function (Event $event): void { + if ($event instanceof AttributeMappedEvent + && $event->getAttribute() === ProviderService::SETTING_MAPPING_DISPLAYNAME + ) { + $event->setValue('Lisa, Mona'); + } + }; + + $this->accountListener = function (Event $event): void { + $this->assertInstanceOf(UserAccountChangeEvent::class, $event); + $this->assertEquals('jgyros', $event->getUid()); + $this->assertEquals('Lisa, Mona', $event->getDisplayName()); + $this->assertEquals('jonny.gyuris@x.y.de', $event->getMainEmail()); + $this->assertNull($event->getQuota()); + + $event->setResult(false, 'not an original', null); + }; + + $this->dispatcher->addListener(AttributeMappedEvent::class, $this->attributeListener); + $this->dispatcher->addListener(UserAccountChangeEvent::class, $this->accountListener); + + $result = $this->loginController->code($this->getOidTestState(), $this->getOidTestCode(), ''); + + $this->assertLogin403($result); + } + + public function testMainEmailMap_Nok_Redirect(): void { + $this->attributeListener = function (Event $event): void { + if ($event instanceof AttributeMappedEvent + && $event->getAttribute() === ProviderService::SETTING_MAPPING_EMAIL + ) { + $event->setValue('mona.lisa@louvre.fr'); + } + }; + + $this->accountListener = function (Event $event): void { + $this->assertInstanceOf(UserAccountChangeEvent::class, $event); + $this->assertEquals('jgyros', $event->getUid()); + $this->assertEquals('Jonny G', $event->getDisplayName()); + $this->assertEquals('mona.lisa@louvre.fr', $event->getMainEmail()); + $this->assertNull($event->getQuota()); + + $event->setResult(false, 'under restoration', 'https://welcome.to.louvre'); + }; + + $this->dispatcher->addListener(AttributeMappedEvent::class, $this->attributeListener); + $this->dispatcher->addListener(UserAccountChangeEvent::class, $this->accountListener); + + $result = $this->loginController->code($this->getOidTestState(), $this->getOidTestCode(), ''); + + $this->assertLoginRedirect($result); + $this->assertEquals('https://welcome.to.louvre', $result->getRedirectURL()); + } + + public function testDisplaynameUidQuotaMapped_AccessOK(): void { + $this->mockAssertLoginSuccess(); + + $this->attributeListener = function (Event $event): void { + if (!$event instanceof AttributeMappedEvent) { + return; + } + + if ($event->getAttribute() === ProviderService::SETTING_MAPPING_DISPLAYNAME) { + $event->setValue('Lisa, Mona'); + } + + if ($event->getAttribute() === ProviderService::SETTING_MAPPING_QUOTA) { + $event->setValue('5 TB'); + } + }; + + $this->accountListener = function (Event $event): void { + $this->assertInstanceOf(UserAccountChangeEvent::class, $event); + $this->assertEquals('jgyros', $event->getUid()); + $this->assertEquals('Lisa, Mona', $event->getDisplayName()); + $this->assertEquals('jonny.gyuris@x.y.de', $event->getMainEmail()); + $this->assertEquals('5 TB', $event->getQuota()); + + $event->setResult(true, 'ok', 'https://welcome.to.louvre'); + }; + + $this->dispatcher->addListener(AttributeMappedEvent::class, $this->attributeListener); + $this->dispatcher->addListener(UserAccountChangeEvent::class, $this->accountListener); + + $result = $this->loginController->code($this->getOidTestState(), $this->getOidTestCode(), ''); + + $this->assertLoginRedirect($result); + $this->assertEquals('http://localhost', $result->getRedirectURL()); + } +} diff --git a/tests/unit/MagentaCloud/RegistrationsTest.php b/tests/unit/MagentaCloud/RegistrationsTest.php new file mode 100644 index 000000000..b5a2eac6e --- /dev/null +++ b/tests/unit/MagentaCloud/RegistrationsTest.php @@ -0,0 +1,33 @@ +app = new Application(); + + $coordinator = \OC::$server->get(Coordinator::class); + $this->app->register($coordinator->getRegistrationContext()->for(Application::APP_ID)); + } + + public function testProvisioningServiceRegistration(): void { + $provisioningService = $this->app->getContainer()->get(ProvisioningService::class); + + $this->assertInstanceOf(ProvisioningEventService::class, $provisioningService); + } +} From d8cf8d15259f20925ed3118574639c3b06febc52 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 16:43:27 +0200 Subject: [PATCH 35/57] fixed coding style --- .../unit/MagentaCloud/OpenidTokenTestCase.php | 45 +++++++--------- .../ProvisioningEventServiceTest.php | 52 +++++++++---------- 2 files changed, 45 insertions(+), 52 deletions(-) diff --git a/tests/unit/MagentaCloud/OpenidTokenTestCase.php b/tests/unit/MagentaCloud/OpenidTokenTestCase.php index ecc648c21..7d1a38949 100644 --- a/tests/unit/MagentaCloud/OpenidTokenTestCase.php +++ b/tests/unit/MagentaCloud/OpenidTokenTestCase.php @@ -5,14 +5,7 @@ namespace OCA\UserOIDC\BaseTest; use OCA\UserOIDC\AppInfo\Application; -use OCA\UserOIDC\Vendor\Firebase\JWT\JWK as FirebaseJWK; -use OCA\UserOIDC\Vendor\Firebase\JWT\Key; -use OCA\UserOIDC\Vendor\Jose\Component\Core\AlgorithmManager; -use OCA\UserOIDC\Vendor\Jose\Component\Core\JWK; use OCA\UserOIDC\Vendor\Jose\Component\Core\Util\Base64UrlSafe; -use OCA\UserOIDC\Vendor\Jose\Component\Signature\Algorithm\RS256; -use OCA\UserOIDC\Vendor\Jose\Component\Signature\JWSBuilder; -use OCA\UserOIDC\Vendor\Jose\Component\Signature\Serializer\CompactSerializer; use OCP\AppFramework\App; use PHPUnit\Framework\TestCase; @@ -39,9 +32,9 @@ public function getOidNonce(): string { return 'CVMI8I3JZPALSL5DIM6I1PDP8SDSEN4K'; } - public function getOidClientSecret(): string { - return 'JQ17C99A-DAF8-4E27-FBW4-GV23B043C993'; - } + public function getOidClientSecret(): string { + return 'JQ17C99A-DAF8-4E27-FBW4-GV23B043C993'; + } public function getOidServerKey(): string { return Base64UrlSafe::encodeUnpadded('JQ17DAF8-C99A-4E27-FBW4-GV23B043C993'); @@ -65,14 +58,14 @@ public function getOidPrivateServerKey(): array { ]; } - public function getOidPublicServerKey(): array { - return [ - '0123456789' => new \OCA\UserOIDC\Vendor\Firebase\JWT\Key( - $this->getOidClientSecret(), - 'HS256', - ), - ]; - } + public function getOidPublicServerKey(): array { + return [ + '0123456789' => new \OCA\UserOIDC\Vendor\Firebase\JWT\Key( + $this->getOidClientSecret(), + 'HS256', + ), + ]; + } public function getOidTestCode(): string { return '66844608'; @@ -111,12 +104,12 @@ public function setUp(): void { ]; } - protected function createSignToken(array $claims): string { - return \OCA\UserOIDC\Vendor\Firebase\JWT\JWT::encode( - $claims, - $this->getOidClientSecret(), - 'HS256', - '0123456789', - ); - } + protected function createSignToken(array $claims): string { + return \OCA\UserOIDC\Vendor\Firebase\JWT\JWT::encode( + $claims, + $this->getOidClientSecret(), + 'HS256', + '0123456789', + ); + } } diff --git a/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php b/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php index 2a413e1c8..728fab851 100644 --- a/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php +++ b/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php @@ -180,21 +180,21 @@ protected function getOidSessionSetup(): MockObject { } protected function getProviderSetup(): Provider { - $provider = new Provider(); - $provider->setId($this->getProviderId()); - $provider->setIdentifier('telekom'); - $provider->setClientId($this->getOidClientId()); - $provider->setClientSecret($this->crypto->encrypt($this->getOidClientSecret())); - $provider->setScope('openid'); - $provider->setDiscoveryEndpoint('https://accounts.login00.custom.de/.well-known/openid-configuration'); - - $this->providerMapper->expects($this->any()) - ->method('getProvider') - ->with($this->equalTo($this->getProviderId())) - ->willReturn($provider); - - return $provider; - } + $provider = new Provider(); + $provider->setId($this->getProviderId()); + $provider->setIdentifier('telekom'); + $provider->setClientId($this->getOidClientId()); + $provider->setClientSecret($this->crypto->encrypt($this->getOidClientSecret())); + $provider->setScope('openid'); + $provider->setDiscoveryEndpoint('https://accounts.login00.custom.de/.well-known/openid-configuration'); + + $this->providerMapper->expects($this->any()) + ->method('getProvider') + ->with($this->equalTo($this->getProviderId())) + ->willReturn($provider); + + return $provider; + } protected function getProviderServiceSetup(): MockObject { $providerService = $this->getMockBuilder(ProviderService::class) @@ -579,18 +579,18 @@ public function testDisplaynameUidQuotaMapped_AccessOK(): void { $this->mockAssertLoginSuccess(); $this->attributeListener = function (Event $event): void { - if (!$event instanceof AttributeMappedEvent) { - return; - } + if (!$event instanceof AttributeMappedEvent) { + return; + } - if ($event->getAttribute() === ProviderService::SETTING_MAPPING_DISPLAYNAME) { - $event->setValue('Lisa, Mona'); - } + if ($event->getAttribute() === ProviderService::SETTING_MAPPING_DISPLAYNAME) { + $event->setValue('Lisa, Mona'); + } - if ($event->getAttribute() === ProviderService::SETTING_MAPPING_QUOTA) { - $event->setValue('5 TB'); - } - }; + if ($event->getAttribute() === ProviderService::SETTING_MAPPING_QUOTA) { + $event->setValue('5 TB'); + } + }; $this->accountListener = function (Event $event): void { $this->assertInstanceOf(UserAccountChangeEvent::class, $event); @@ -608,6 +608,6 @@ public function testDisplaynameUidQuotaMapped_AccessOK(): void { $result = $this->loginController->code($this->getOidTestState(), $this->getOidTestCode(), ''); $this->assertLoginRedirect($result); - $this->assertEquals('http://localhost', $result->getRedirectURL()); + $this->assertEquals('http://localhost', $result->getRedirectURL()); } } From 7c481568c55bafa9bf84e360da90bc58f1a89657 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 17:04:33 +0200 Subject: [PATCH 36/57] fixed provisioning --- lib/Event/UserAccountChangeEvent.php | 3 +-- lib/Event/UserAccountChangeResult.php | 8 ++++++-- lib/Service/ProvisioningEventService.php | 11 +++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/Event/UserAccountChangeEvent.php b/lib/Event/UserAccountChangeEvent.php index 94a2064d0..718e3296a 100644 --- a/lib/Event/UserAccountChangeEvent.php +++ b/lib/Event/UserAccountChangeEvent.php @@ -23,11 +23,10 @@ public function __construct( private ?string $mainEmail, private ?string $quota, private object $claims, - bool $accessAllowed = false, ) { parent::__construct(); - $this->result = new UserAccountChangeResult($accessAllowed, 'default'); + $this->result = new UserAccountChangeResult(); } public function getUid(): string { diff --git a/lib/Event/UserAccountChangeResult.php b/lib/Event/UserAccountChangeResult.php index aace323ab..1a8bb43ab 100644 --- a/lib/Event/UserAccountChangeResult.php +++ b/lib/Event/UserAccountChangeResult.php @@ -14,14 +14,18 @@ */ class UserAccountChangeResult { public function __construct( - private bool $accessAllowed, + private ?bool $accessAllowed = null, private string $reason = '', private ?string $redirectUrl = null, ) { } + public function hasDecision(): bool { + return $this->accessAllowed !== null; + } + public function isAccessAllowed(): bool { - return $this->accessAllowed; + return $this->accessAllowed === true; } public function setAccessAllowed(bool $accessAllowed): void { diff --git a/lib/Service/ProvisioningEventService.php b/lib/Service/ProvisioningEventService.php index 4fe22ed3e..239a6946d 100644 --- a/lib/Service/ProvisioningEventService.php +++ b/lib/Service/ProvisioningEventService.php @@ -13,6 +13,7 @@ use OCA\UserOIDC\Db\UserMapper; use OCA\UserOIDC\Event\AttributeMappedEvent; use OCA\UserOIDC\Event\UserAccountChangeEvent; +use OCA\UserOIDC\Event\UserAccountChangeResult; use OCP\Accounts\IAccountManager; use OCP\DB\Exception; use OCP\EventDispatcher\IEventDispatcher; @@ -133,11 +134,17 @@ protected function dispatchUserAccountUpdate( ?string $email, ?string $quota, object $payload, - ): mixed { + ): UserAccountChangeResult { $event = new UserAccountChangeEvent($uid, $displayName, $email, $quota, $payload); $this->eventDispatcher->dispatchTyped($event); - return $event->getResult(); + $result = $event->getResult(); + + if ($result->hasDecision() && !$result->isAccessAllowed()) { + throw new ProvisioningDeniedException($result->getReason()); + } + + return $result; } /** From 930c21a62dcd2486ce189dc2ae15ed1dc2000363 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 17:09:45 +0200 Subject: [PATCH 37/57] fix --- lib/Service/ProvisioningEventService.php | 33 ++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/Service/ProvisioningEventService.php b/lib/Service/ProvisioningEventService.php index 239a6946d..a3bf8d71d 100644 --- a/lib/Service/ProvisioningEventService.php +++ b/lib/Service/ProvisioningEventService.php @@ -141,7 +141,10 @@ protected function dispatchUserAccountUpdate( $result = $event->getResult(); if ($result->hasDecision() && !$result->isAccessAllowed()) { - throw new ProvisioningDeniedException($result->getReason()); + throw new ProvisioningDeniedException( + $result->getReason(), + $result->getRedirectUrl(), + ); } return $result; @@ -174,20 +177,24 @@ public function provisionUser( $userReaction = $this->dispatchUserAccountUpdate($uid, $displayName, $email, $quota, $idTokenPayload); - if ($userReaction->isAccessAllowed()) { - $this->logger->info($uid . ': account accepted, reason: ' . $userReaction->getReason()); + if ($userReaction->hasDecision()) { + if ($userReaction->isAccessAllowed()) { + $this->logger->info($uid . ': account accepted, reason: ' . $userReaction->getReason()); - return [ - 'user' => $existingLocalUser ?? $this->userManager->get($uid), - 'userData' => get_object_vars($idTokenPayload), - ]; - } + return [ + 'user' => $existingLocalUser ?? $this->userManager->get($uid), + 'userData' => get_object_vars($idTokenPayload), + ]; + } - $this->logger->info($uid . ': account rejected, reason: ' . $userReaction->getReason()); + $this->logger->info($uid . ': account rejected, reason: ' . $userReaction->getReason()); - throw new ProvisioningDeniedException( - $userReaction->getReason(), - $userReaction->getRedirectUrl(), - ); + throw new ProvisioningDeniedException( + $userReaction->getReason(), + $userReaction->getRedirectUrl(), + ); + } + + return parent::provisionUser($tokenUserId, $providerId, $idTokenPayload, $existingLocalUser); } } From 432f94e3c018e6f86d74e3ee96d04f442dadba99 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 17:26:24 +0200 Subject: [PATCH 38/57] remove user api endpoints --- appinfo/routes.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 91f8656b7..f5e7890e8 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -33,7 +33,8 @@ ['name' => 'Settings#getSupportedSettings', 'url' => '/api/{apiVersion}/supported-settings', 'verb' => 'GET', 'requirements' => $requirements], ['name' => 'Settings#setAdminConfig', 'url' => '/api/{apiVersion}/admin-config', 'verb' => 'POST', 'requirements' => $requirements], - ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], - ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], + // We have to disable the endpoints to avoid problems with Telekom provisioning + // ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], + // ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], ], ]; From 73862c92bc277c131c3c98a29db34967b0636c71 Mon Sep 17 00:00:00 2001 From: memurats Date: Wed, 6 May 2026 17:43:15 +0200 Subject: [PATCH 39/57] update workflow options --- .github/workflows/nmc-custom-app-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nmc-custom-app-release.yml b/.github/workflows/nmc-custom-app-release.yml index 64d287cef..ec889c38c 100644 --- a/.github/workflows/nmc-custom-app-release.yml +++ b/.github/workflows/nmc-custom-app-release.yml @@ -27,9 +27,9 @@ on: description: Branch to build a package from options: - main - - stable25 - - stable26 - - stable27 + - stable32 + - stable33 + - stable34 default: main jobs: From 754d55b5d2941d8db5e1c0e47dc3118042da8979 Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Wed, 6 May 2026 18:01:23 +0200 Subject: [PATCH 40/57] Disable user API endpoints for Telekom provisioning Commented out user-related API endpoints to prevent issues. --- appinfo/routes.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index f5e7890e8..5ab4ea73b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -32,9 +32,5 @@ ['name' => 'Settings#setID4ME', 'url' => '/api/{apiVersion}/provider/id4me', 'verb' => 'POST', 'requirements' => $requirements], ['name' => 'Settings#getSupportedSettings', 'url' => '/api/{apiVersion}/supported-settings', 'verb' => 'GET', 'requirements' => $requirements], ['name' => 'Settings#setAdminConfig', 'url' => '/api/{apiVersion}/admin-config', 'verb' => 'POST', 'requirements' => $requirements], - - // We have to disable the endpoints to avoid problems with Telekom provisioning - // ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], - // ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], ], ]; From 76a858235d12f6f7148b947551560eff43cec934 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 08:48:10 +0200 Subject: [PATCH 41/57] fix --- lib/AppInfo/Application.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 4f3a0e942..d7e5a620a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -26,6 +26,7 @@ use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; +// use OCA\UserOIDC\User\Backend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; From eadbe44609b8f6fff7d0b185dbd254157230a0be Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Thu, 7 May 2026 08:53:10 +0200 Subject: [PATCH 42/57] Restore user endpoints in API routes Re-enable user creation and deletion endpoints in the API routes. --- appinfo/routes.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index d357f42cf..faf0ae16b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -31,9 +31,8 @@ ['name' => 'Settings#setID4ME', 'url' => '/api/{apiVersion}/provider/id4me', 'verb' => 'POST', 'requirements' => $requirements], ['name' => 'Settings#getSupportedSettings', 'url' => '/api/{apiVersion}/supported-settings', 'verb' => 'GET', 'requirements' => $requirements], ['name' => 'Settings#setAdminConfig', 'url' => '/api/{apiVersion}/admin-config', 'verb' => 'POST', 'requirements' => $requirements], - - // We have to disable the endpoints to avoid problems with Telekom provisioning - // ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], - // ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], + + ['name' => 'ocsApi#createUser', 'url' => '/api/{apiVersion}/user', 'verb' => 'POST', 'requirements' => $requirements], + ['name' => 'ocsApi#deleteUser', 'url' => '/api/{apiVersion}/user/{userId}', 'verb' => 'DELETE', 'requirements' => $requirements], ], ]; From d74b8e40e43b7b48a43efc8378063a9e825f4fe4 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:11:04 +0200 Subject: [PATCH 43/57] prevent merge conflict --- lib/AppInfo/Application.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d7e5a620a..930cda683 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,6 +23,8 @@ use OCA\UserOIDC\Listener\TokenInvalidatedListener; use OCA\UserOIDC\MagentaBearer\MBackend; use OCA\UserOIDC\Service\ID4MeService; +use OCA\UserOIDC\Service\ProvisioningEventService; +use OCA\UserOIDC\Service\ProvisioningService; use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; @@ -39,6 +41,7 @@ use OCP\IUserManager; use OCP\IUserSession; use OCP\Security\ISecureRandom; +use Psr\Container\ContainerInterface; use Throwable; class Application extends App implements IBootstrap { From 77788d1559e92a2002fbc8d8baaa633dde321899 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:21:36 +0200 Subject: [PATCH 44/57] fix --- lib/AppInfo/Application.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 930cda683..1f646247f 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,8 +23,6 @@ use OCA\UserOIDC\Listener\TokenInvalidatedListener; use OCA\UserOIDC\MagentaBearer\MBackend; use OCA\UserOIDC\Service\ID4MeService; -use OCA\UserOIDC\Service\ProvisioningEventService; -use OCA\UserOIDC\Service\ProvisioningService; use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; @@ -40,8 +38,9 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; + +// this is needed only for the special, shortened client login flow use OCP\Security\ISecureRandom; -use Psr\Container\ContainerInterface; use Throwable; class Application extends App implements IBootstrap { From 18650bb6c24a8045d0fad946ed72ac3224187b59 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:24:11 +0200 Subject: [PATCH 45/57] fix --- lib/AppInfo/Application.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 6958a6c4d..abfa5c0fd 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -38,6 +38,8 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; + +// this is needed only for the event-based provisioning solution use Psr\Container\ContainerInterface; use Throwable; From 628bbebeba97a68f2bfab95e7a7beb1206ab282b Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:38:05 +0200 Subject: [PATCH 46/57] added custom client flow --- lib/AppInfo/Application.php | 54 ++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 192220cab..cbd04843d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -24,7 +24,6 @@ use OCA\UserOIDC\Service\ID4MeService; use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; -use OCA\UserOIDC\Service\TokenService; use OCA\UserOIDC\User\Backend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -36,6 +35,7 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; +use OCP\Security\ISecureRandom; use Throwable; class Application extends App implements IBootstrap { @@ -84,7 +84,6 @@ public function register(IRegistrationContext $context): void { public function boot(IBootContext $context): void { $context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession'])); - $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken'])); /** @var IUserSession $userSession */ $userSession = $this->getContainer()->get(IUserSession::class); if ($userSession->isLoggedIn()) { @@ -93,6 +92,7 @@ public function boot(IBootContext $context): void { try { $context->injectFn(\Closure::fromCallable([$this, 'registerRedirect'])); + $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow'])); if (version_compare($this->getContainer()->get(IConfig::class)->getSystemValueString('version', '0.0.0'), '34.0.0', '<')) { $context->injectFn(\Closure::fromCallable([$this, 'registerLogin'])); } @@ -100,8 +100,54 @@ public function boot(IBootContext $context): void { } } - private function checkLoginToken(TokenService $tokenService): void { - $tokenService->checkLoginToken(); + /** + * This is the automatic redirect exclusively for Nextcloud/Magentacloud clients, completely skipping consent layer. + */ + private function registerNmcClientFlow( + IRequest $request, + IURLGenerator $urlGenerator, + ProviderMapper $providerMapper, + ISession $session, + ISecureRandom $random, + ): void { + $providers = $this->getCachedProviders($providerMapper); + + try { + $isClientLoginFlow = $request->getPathInfo() === '/login/flow'; + } catch (Exception) { + return; + } + + if (!$isClientLoginFlow) { + return; + } + + $tproviders = array_values(array_filter($providers, static function ($provider): bool { + return strtolower($provider->getIdentifier()) === 'telekom'; + })); + + if (count($tproviders) === 0) { + return; + } + + $stateToken = $random->generate(64, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); + + $session->set('client.flow.state.token', $stateToken); + + $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [ + 'stateToken' => $stateToken, + 'clientIdentifier' => $request->getParam('clientIdentifier', ''), + 'direct' => $request->getParam('direct', '0'), + ]); + + $targetUrl = $urlGenerator->linkToRoute(self::APP_ID . '.login.login', [ + 'providerId' => $tproviders[0]->getId(), + 'redirectUrl' => $redirectUrl, + ]); + + header('Location: ' . $targetUrl); + + exit(); } private function registerRedirect(IRequest $request, IURLGenerator $urlGenerator, SettingsService $settings, ProviderMapper $providerMapper): void { From 8c97227254bf838de3f4bc6b359dd659e91a4d79 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:42:35 +0200 Subject: [PATCH 47/57] remove flow --- lib/AppInfo/Application.php | 58 +------------------------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 1f646247f..f90335a72 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -26,7 +26,6 @@ use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; use OCA\UserOIDC\Service\TokenService; -// use OCA\UserOIDC\User\Backend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -34,13 +33,9 @@ use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; -use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; - -// this is needed only for the special, shortened client login flow -use OCP\Security\ISecureRandom; use Throwable; class Application extends App implements IBootstrap { @@ -89,7 +84,7 @@ public function register(IRegistrationContext $context): void { public function boot(IBootContext $context): void { $context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession'])); - // $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken'])); + $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken'])); /** @var IUserSession $userSession */ $userSession = $this->getContainer()->get(IUserSession::class); if ($userSession->isLoggedIn()) { @@ -98,7 +93,6 @@ public function boot(IBootContext $context): void { try { $context->injectFn(\Closure::fromCallable([$this, 'registerRedirect'])); - $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow'])); if (version_compare($this->getContainer()->get(IConfig::class)->getSystemValueString('version', '0.0.0'), '34.0.0', '<')) { $context->injectFn(\Closure::fromCallable([$this, 'registerLogin'])); } @@ -164,56 +158,6 @@ private function registerLogin( } } - /** - * This is the automatic redirect exclusively for Nextcloud/Magentacloud clients, completely skipping consent layer. - */ - private function registerNmcClientFlow( - IRequest $request, - IURLGenerator $urlGenerator, - ProviderMapper $providerMapper, - ISession $session, - ISecureRandom $random, - ): void { - $providers = $this->getCachedProviders($providerMapper); - - try { - $isClientLoginFlow = $request->getPathInfo() === '/login/flow'; - } catch (Exception) { - return; - } - - if (!$isClientLoginFlow) { - return; - } - - $tproviders = array_values(array_filter($providers, static function ($provider): bool { - return strtolower($provider->getIdentifier()) === 'telekom'; - })); - - if (count($tproviders) === 0) { - return; - } - - $stateToken = $random->generate(64, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); - - $session->set('client.flow.state.token', $stateToken); - - $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [ - 'stateToken' => $stateToken, - 'clientIdentifier' => $request->getParam('clientIdentifier', ''), - 'direct' => $request->getParam('direct', '0'), - ]); - - $targetUrl = $urlGenerator->linkToRoute(self::APP_ID . '.login.login', [ - 'providerId' => $tproviders[0]->getId(), - 'redirectUrl' => $redirectUrl, - ]); - - header('Location: ' . $targetUrl); - - exit(); - } - private function getCachedProviders(ProviderMapper $providerMapper): array { if (!isset($this->cachedProviders)) { $this->cachedProviders = $providerMapper->getProviders(); From 635526750abfeefe7066ce695ed734245f048d32 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:43:23 +0200 Subject: [PATCH 48/57] added session class --- lib/AppInfo/Application.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index cbd04843d..f85f1ea44 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -32,6 +32,7 @@ use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; +use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; From 93de64c43e684d0d5ae26c8e7b6d107dee8efbd6 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 09:47:27 +0200 Subject: [PATCH 49/57] fixed coding style --- lib/AppInfo/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index f85f1ea44..d5a568aab 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -93,7 +93,7 @@ public function boot(IBootContext $context): void { try { $context->injectFn(\Closure::fromCallable([$this, 'registerRedirect'])); - $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow'])); + $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow'])); if (version_compare($this->getContainer()->get(IConfig::class)->getSystemValueString('version', '0.0.0'), '34.0.0', '<')) { $context->injectFn(\Closure::fromCallable([$this, 'registerLogin'])); } From 771c89cffb6ad142f93f6d3b22c72d1e9a00700c Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Thu, 7 May 2026 10:16:59 +0200 Subject: [PATCH 50/57] Update OIDC composer workflow and dependencies Updated author information, upgraded checkout action, and modified composer dependency handling in the workflow. --- .../workflows/nmc-custom-oidc-composer.yml | 91 +++++++++++++++++-- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index 8066b60d6..c1b9d0a5d 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -1,10 +1,12 @@ ### # SPDX-License-Identifier: AGPL-3.0 # -# Author: Bernd Rederlechner +# Author: Mauro Mura # # user_oidc brings its PHP dependencies via composer.json. # composer install also runs Mozart via post-install-cmd. +# We add these commandline based in build to avoid continuous +# merge conflicts due to "composer.lock" merge problems. name: MCLOUD custom user_oidc dependencies @@ -20,24 +22,31 @@ jobs: build-custom: runs-on: ubuntu-latest env: + BUILD_USER: ${{ github.actor }} + BUILD_EMAIL: ${{ github.actor }}@users.noreply.github.com BUILD_TOKEN: ${{ secrets.BUILD_TOKEN || secrets.GITHUB_TOKEN }} PHP_VERSION: ${{ vars.PHP_VERSION || '8.3' }} + ASSEMBLY_BRANCH: ${{ inputs.assembly }} steps: - name: Fetch custom assembly - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ github.repository }} ref: ${{ inputs.assembly }} fetch-depth: 0 token: ${{ env.BUILD_TOKEN }} + - name: Prepare GIT modifications + run: | + git config user.name "$BUILD_USER" + git config user.email "$BUILD_EMAIL" + - name: Set up PHP ${{ env.PHP_VERSION }} uses: shivammathur/setup-php@v2 with: php-version: ${{ env.PHP_VERSION }} coverage: none - tools: composer - name: Check composer.json id: check_composer @@ -45,12 +54,80 @@ jobs: with: files: "./composer.json" - - name: Install composer dependencies and build prefixed vendor + - name: Patch composer.json for custom user_oidc dependencies + if: steps.check_composer.outputs.files_exists == 'true' + run: | + php <<'PHP' + Date: Thu, 7 May 2026 10:19:04 +0200 Subject: [PATCH 51/57] revert composer files to standard --- composer.json | 77 +- composer.lock | 2333 +++++-------------------------------------------- 2 files changed, 265 insertions(+), 2145 deletions(-) diff --git a/composer.json b/composer.json index 83f6e2ae9..697b9427c 100644 --- a/composer.json +++ b/composer.json @@ -9,16 +9,6 @@ "bamarni/composer-bin-plugin": true } }, - "autoload": { - "psr-4": { - "OCA\\UserOIDC\\": "lib/" - } - }, - "autoload-dev": { - "psr-4": { - "OCA\\UserOIDC\\Tests\\": "tests/" - } - }, "scripts": { "cs:fix": "php-cs-fixer fix", "cs:check": "php-cs-fixer fix --dry-run --diff", @@ -28,32 +18,22 @@ "psalm:update-baseline": "psalm --threads=1 --update-baseline", "psalm:update-baseline:force": "psalm --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml", "openapi": "generate-spec --verbose", - "post-install-cmd": [ "@composer bin all install --ansi", - "vendor/bin/mozart compose", - "@fix-prefixed-aeskw-imports", + "\"vendor/bin/mozart\" compose", "composer dump-autoload" ], "post-update-cmd": [ "@composer bin all install --ansi", - "vendor/bin/mozart compose", - "@fix-prefixed-aeskw-imports", + "\"vendor/bin/mozart\" compose", "composer dump-autoload" - ], - "fix-prefixed-aeskw-imports": "[ ! -d lib/Vendor/Jose/Component/Encryption/Algorithm/KeyEncryption ] || find lib/Vendor/Jose/Component/Encryption/Algorithm/KeyEncryption -type f -name '*.php' -exec sed -i -e 's/use AESKW\\\\/use OCA\\\\UserOIDC\\\\Vendor\\\\AESKW\\\\/g' {} +; [ ! -d lib/Vendor/AESKW ] || find lib/Vendor/AESKW -type f -name '*.php' -exec sed -i -e 's/use OCA\\\\UserOIDC\\\\Vendor\\\\AESKW;/use AESKW;/g' {} +; [ ! -f lib/Vendor/AESKW/AESKW.php ] || sed -i -e 's/trait OCA\\\\UserOIDC\\\\Vendor\\\\AESKW/trait AESKW/g' lib/Vendor/AESKW/AESKW.php" + ] }, - "require": { "id4me/id4me-rp": "^1.2", "firebase/php-jwt": "^7", - "bamarni/composer-bin-plugin": "^1.4", - "web-token/jwt-core": "^3.4", - "web-token/jwt-signature": "^3.4", - "web-token/jwt-encryption": "^3.4", - "spomky-labs/aes-key-wrap": "^7.0" + "bamarni/composer-bin-plugin": "^1.4" }, - "require-dev": { "nextcloud/coding-standard": "^1.0.0", "symfony/event-dispatcher": "^7", @@ -61,34 +41,29 @@ "phpunit/phpunit": "^11", "nextcloud/openapi-extractor": "^1.8" }, - "extra": { "mozart": { - "dep_namespace": "OCA\\UserOIDC\\Vendor\\", - "dep_directory": "/lib/Vendor/", - "classmap_directory": "/lib/autoload/", - "classmap_prefix": "NEXTCLOUD_USER_OIDC_", - "packages": [ - "firebase/php-jwt", - "id4me/id4me-rp", - "spomky-labs/aes-key-wrap", - "web-token/jwt-core", - "web-token/jwt-signature", - "web-token/jwt-encryption" - ], - "delete_vendor_directories": true, - "override_autoload": { - "id4me/id4me-rp": { - "psr-4": { - "Id4me\\RP\\": "src/" - } - } - } + "dep_namespace": "OCA\\UserOIDC\\Vendor\\", + "dep_directory": "/lib/Vendor/", + "classmap_directory": "/lib/autoload/", + "classmap_prefix": "NEXTCLOUD_USER_OIDC_", + "packages": [ + "firebase/php-jwt", + "id4me/id4me-rp" + ], + "delete_vendor_directories": true, + "override_autoload": { + "id4me/id4me-rp": { + "psr-4": { + "Id4me\\RP\\": "src/" + } + } + } }, - "bamarni-bin": { - "bin-links": true, - "target-directory": "vendor-bin", - "forward-command": true - } + "bamarni-bin": { + "bin-links": true, + "target-directory": "vendor-bin", + "forward-command": true + } } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index c4af10246..4040c0f59 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": "95d808e08a0169cc660bc75046c0dd31", + "content-hash": "d04dc4373433d9ac88a4fd120a014533", "packages": [ { "name": "bamarni/composer-bin-plugin", @@ -63,66 +63,6 @@ }, "time": "2026-02-04T10:18:12+00:00" }, - { - "name": "brick/math", - "version": "0.12.3", - "source": { - "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Brick\\Math\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Arbitrary-precision arithmetic library", - "keywords": [ - "Arbitrary-precision", - "BigInteger", - "BigRational", - "arithmetic", - "bigdecimal", - "bignum", - "bignumber", - "brick", - "decimal", - "integer", - "math", - "mathematics", - "rational" - ], - "support": { - "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" - }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - } - ], - "time": "2025-02-28T13:11:00+00:00" - }, { "name": "firebase/php-jwt", "version": "v7.0.5", @@ -212,2081 +152,135 @@ "guzzlehttp/guzzle": "^6.2", "guzzlehttp/psr7": "1.5.*", "phpunit/phpunit": "^6.0", - "squizlabs/php_codesniffer": "^3.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Id4me\\RP\\": "src/", - "Id4me\\Test\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Library for ID4me", - "support": { - "issues": "https://gitlab.com/api/v4/projects/11132066/issues" - }, - "abandoned": true, - "time": "2020-09-24T07:04:40+00:00" - }, - { - "name": "paragonie/constant_time_encoding", - "version": "v3.1.3", - "source": { - "type": "git", - "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", - "shasum": "" - }, - "require": { - "php": "^8" - }, - "require-dev": { - "infection/infection": "^0", - "nikic/php-fuzzer": "^0", - "phpunit/phpunit": "^9|^10|^11", - "vimeo/psalm": "^4|^5|^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "ParagonIE\\ConstantTime\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com", - "role": "Maintainer" - }, - { - "name": "Steve 'Sc00bz' Thomas", - "email": "steve@tobtu.com", - "homepage": "https://www.tobtu.com", - "role": "Original Developer" - } - ], - "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", - "keywords": [ - "base16", - "base32", - "base32_decode", - "base32_encode", - "base64", - "base64_decode", - "base64_encode", - "bin2hex", - "encoding", - "hex", - "hex2bin", - "rfc4648" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/constant_time_encoding/issues", - "source": "https://github.com/paragonie/constant_time_encoding" - }, - "time": "2025-09-24T15:06:41+00:00" - }, - { - "name": "paragonie/sodium_compat", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/4714da6efdc782c06690bc72ce34fae7941c2d9f", - "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f", - "shasum": "" - }, - "require": { - "php": "^8.1", - "php-64bit": "*" - }, - "require-dev": { - "infection/infection": "^0", - "nikic/php-fuzzer": "^0", - "phpunit/phpunit": "^7|^8|^9|^10|^11", - "vimeo/psalm": "^4|^5|^6" - }, - "suggest": { - "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "files": [ - "autoload.php" - ], - "psr-4": { - "ParagonIE\\Sodium\\": "namespaced/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com" - }, - { - "name": "Frank Denis", - "email": "jedisct1@pureftpd.org" - } - ], - "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", - "keywords": [ - "Authentication", - "BLAKE2b", - "ChaCha20", - "ChaCha20-Poly1305", - "Chapoly", - "Curve25519", - "Ed25519", - "EdDSA", - "Edwards-curve Digital Signature Algorithm", - "Elliptic Curve Diffie-Hellman", - "Poly1305", - "Pure-PHP cryptography", - "RFC 7748", - "RFC 8032", - "Salpoly", - "Salsa20", - "X25519", - "XChaCha20-Poly1305", - "XSalsa20-Poly1305", - "Xchacha20", - "Xsalsa20", - "aead", - "cryptography", - "ecdh", - "elliptic curve", - "elliptic curve cryptography", - "encryption", - "libsodium", - "php", - "public-key cryptography", - "secret-key cryptography", - "side-channel resistant" - ], - "support": { - "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v2.5.0" - }, - "time": "2025-12-30T16:12:18+00:00" - }, - { - "name": "phpseclib/phpseclib", - "version": "2.0.53", - "source": { - "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2d1a664b940b9b8f367185307dc010d11a2790f3", - "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phing/phing": "~2.7", - "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^8.5|^9.4", - "squizlabs/php_codesniffer": "~2.0" - }, - "suggest": { - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.", - "ext-xml": "Install the XML extension to load XML formatted public keys." - }, - "type": "library", - "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], - "psr-4": { - "phpseclib\\": "phpseclib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } - ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", - "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" - ], - "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.53" - }, - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], - "time": "2026-04-10T01:30:02+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/clock", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/clock.git", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Clock\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for reading the clock.", - "homepage": "https://github.com/php-fig/clock", - "keywords": [ - "clock", - "now", - "psr", - "psr-20", - "time" - ], - "support": { - "issues": "https://github.com/php-fig/clock/issues", - "source": "https://github.com/php-fig/clock/tree/1.0.0" - }, - "time": "2022-11-25T14:36:26+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client" - }, - "time": "2023-09-23T14:17:50+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory" - }, - "time": "2024-04-15T12:06:14+00:00" - }, - { - "name": "psr/http-message", - "version": "2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" - }, - "time": "2023-04-04T09:54:51+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "spomky-labs/aes-key-wrap", - "version": "v7.0.0", - "source": { - "type": "git", - "url": "https://github.com/Spomky-Labs/aes-key-wrap.git", - "reference": "fbeb834b1f83aa8fbdfbd4c12124f71d4c1606ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/aes-key-wrap/zipball/fbeb834b1f83aa8fbdfbd4c12124f71d4c1606ae", - "reference": "fbeb834b1f83aa8fbdfbd4c12124f71d4c1606ae", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "ext-openssl": "*", - "php": ">=8.0" - }, - "require-dev": { - "infection/infection": "^0.25.4", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-beberlei-assert": "^1.0", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.0", - "rector/rector": "^0.12.5", - "symplify/easy-coding-standard": "^10.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AESKW\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky-Labs/aes-key-wrap/contributors" - } - ], - "description": "AES Key Wrap for PHP.", - "homepage": "https://github.com/Spomky-Labs/aes-key-wrap", - "keywords": [ - "A128KW", - "A192KW", - "A256KW", - "RFC3394", - "RFC5649", - "aes", - "key", - "padding", - "wrap" - ], - "support": { - "issues": "https://github.com/Spomky-Labs/aes-key-wrap/issues", - "source": "https://github.com/Spomky-Labs/aes-key-wrap/tree/v7.0.0" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2021-12-08T20:36:59+00:00" - }, - { - "name": "spomky-labs/pki-framework", - "version": "1.4.2", - "source": { - "type": "git", - "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/aa576cbd07128075bef97ac2f8af9854e67513d8", - "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8", - "shasum": "" - }, - "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17", - "ext-mbstring": "*", - "php": ">=8.1", - "psr/clock": "^1.0" - }, - "require-dev": { - "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", - "ext-gmp": "*", - "ext-openssl": "*", - "infection/infection": "^0.28|^0.29|^0.31|^0.32", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/extension-installer": "^1.3|^2.0", - "phpstan/phpstan": "^1.8|^2.0", - "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", - "phpstan/phpstan-phpunit": "^1.1|^2.0", - "phpstan/phpstan-strict-rules": "^1.3|^2.0", - "phpunit/phpunit": "^10.1|^11.0|^12.0|^13.0", - "rector/rector": "^1.0|^2.0", - "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symplify/easy-coding-standard": "^12.0|^13.0" - }, - "suggest": { - "ext-bcmath": "For better performance (or GMP)", - "ext-gmp": "For better performance (or BCMath)", - "ext-openssl": "For OpenSSL based cyphering" - }, - "type": "library", - "autoload": { - "psr-4": { - "SpomkyLabs\\Pki\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joni Eskelinen", - "email": "jonieske@gmail.com", - "role": "Original developer" - }, - { - "name": "Florent Morselli", - "email": "florent.morselli@spomky-labs.com", - "role": "Spomky-Labs PKI Framework developer" - } - ], - "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", - "homepage": "https://github.com/spomky-labs/pki-framework", - "keywords": [ - "DER", - "Private Key", - "ac", - "algorithm identifier", - "asn.1", - "asn1", - "attribute certificate", - "certificate", - "certification request", - "cryptography", - "csr", - "decrypt", - "ec", - "encrypt", - "pem", - "pkcs", - "public key", - "rsa", - "sign", - "signature", - "verify", - "x.509", - "x.690", - "x509", - "x690" - ], - "support": { - "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.2" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2026-03-23T22:56:56+00:00" - }, - { - "name": "symfony/console", - "version": "v7.4.9", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", - "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v7.4.9" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-04-22T15:21:55+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/http-client", - "version": "v7.4.9", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", - "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.4|^3.5.2", - "symfony/polyfill-php83": "^1.29", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "amphp/amp": "<2.5", - "amphp/socket": "<1.1", - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/http-client": "^4.2.1|^5.0", - "amphp/http-tunnel": "^1.0|^2.0", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.9" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-04-29T13:25:15+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-04-29T11:18:49+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-04-10T16:19:22+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-04-26T13:13:48+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-04-10T17:25:58+00:00" - }, - { - "name": "symfony/polyfill-php83", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-04-10T17:25:58+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.6.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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-07-15T11:30:57+00:00" - }, - { - "name": "symfony/string", - "version": "v7.4.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "114ac57257d75df748eda23dd003878080b8e688" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", - "reference": "114ac57257d75df748eda23dd003878080b8e688", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v7.4.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "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": "2026-03-24T13:12:05+00:00" - }, - { - "name": "web-token/jwt-core", - "version": "3.4.8", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-core.git", - "reference": "0a47aa6096024af3bff8082e47e27219b9889542" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-core/zipball/0a47aa6096024af3bff8082e47e27219b9889542", - "reference": "0a47aa6096024af3bff8082e47e27219b9889542", - "shasum": "" - }, - "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12", - "ext-json": "*", - "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.6|^3.0", - "php": ">=8.1", - "spomky-labs/pki-framework": "^1.2.1", - "web-token/jwt-library": "^3.3" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-framework/contributors" - } - ], - "description": "[DEPRECATED] Please use web-token/jwt-library instead.", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "source": "https://github.com/web-token/jwt-core/tree/3.4.8" - }, - "funding": [ - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "abandoned": "web-token/jwt-library", - "time": "2024-06-24T16:31:57+00:00" - }, - { - "name": "web-token/jwt-encryption", - "version": "3.4.8", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-encryption.git", - "reference": "3e4b1c4080a77a6e97b62b33562805c1fc552b5e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-encryption/zipball/3e4b1c4080a77a6e97b62b33562805c1fc552b5e", - "reference": "3e4b1c4080a77a6e97b62b33562805c1fc552b5e", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "web-token/jwt-library": "^3.3" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-framework/contributors" - } - ], - "description": "[DEPRECATED] Please use web-token/jwt-library instead.", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "source": "https://github.com/web-token/jwt-encryption/tree/3.4.8" - }, - "funding": [ - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "abandoned": "web-token/jwt-library", - "time": "2024-02-22T07:19:34+00:00" - }, - { - "name": "web-token/jwt-library", - "version": "3.4.9", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-library.git", - "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f", - "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f", - "shasum": "" - }, - "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12", - "ext-json": "*", - "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.6|^3.0", - "paragonie/sodium_compat": "^1.20|^2.0", - "php": ">=8.1", - "psr/cache": "^2.0|^3.0", - "psr/clock": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "spomky-labs/pki-framework": "^1.2.1", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.12" - }, - "conflict": { - "spomky-labs/jose": "*" - }, - "suggest": { - "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", - "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", - "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", - "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW...)", - "symfony/http-client": "To enable JKU/X5U support." + "squizlabs/php_codesniffer": "^3.4" }, "type": "library", "autoload": { "psr-4": { - "Jose\\Component\\": "" + "Id4me\\RP\\": "src/", + "Id4me\\Test\\": "tests/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-framework/contributors" - } - ], - "description": "JWT library", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], + "description": "Library for ID4me", "support": { - "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.9" + "issues": "https://gitlab.com/api/v4/projects/11132066/issues" }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2025-11-17T20:20:37+00:00" + "abandoned": true, + "time": "2020-09-24T07:04:40+00:00" }, { - "name": "web-token/jwt-signature", - "version": "3.4.8", + "name": "phpseclib/phpseclib", + "version": "2.0.53", "source": { "type": "git", - "url": "https://github.com/web-token/jwt-signature.git", - "reference": "eccfd59e658d4118414cf6d14229aa52eec387e7" + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/eccfd59e658d4118414cf6d14229aa52eec387e7", - "reference": "eccfd59e658d4118414cf6d14229aa52eec387e7", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2d1a664b940b9b8f367185307dc010d11a2790f3", + "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3", "shasum": "" }, "require": { - "php": ">=8.1", - "web-token/jwt-library": "^3.3" + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^8.5|^9.4", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.", + "ext-xml": "Install the XML extension to load XML formatted public keys." }, "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" }, { - "name": "All contributors", - "homepage": "https://github.com/web-token/jwt-framework/contributors" + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" } ], - "description": "[DEPRECATED] Please use web-token/jwt-library instead.", - "homepage": "https://github.com/web-token", + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" ], "support": { - "source": "https://github.com/web-token/jwt-signature/tree/3.4.8" + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.53" }, "funding": [ { - "url": "https://www.patreon.com/FlorentMorselli", + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" } ], - "abandoned": "web-token/jwt-library", - "time": "2024-02-22T07:19:34+00:00" + "time": "2026-04-10T01:30:02+00:00" } ], "packages-dev": [ @@ -3343,6 +1337,107 @@ ], "time": "2026-02-18T12:37:06+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, { "name": "psr/event-dispatcher", "version": "1.0.0", @@ -3393,6 +1488,56 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, { "name": "sebastian/cli-parser", "version": "3.0.2", @@ -4653,4 +2798,4 @@ "platform": {}, "platform-dev": {}, "plugin-api-version": "2.9.0" -} +} \ No newline at end of file From 26db0d6f06afabb36c680e271f324f96593dafa3 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 10:21:26 +0200 Subject: [PATCH 52/57] added original files --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 697b9427c..49b50b80e 100644 --- a/composer.json +++ b/composer.json @@ -66,4 +66,4 @@ "forward-command": true } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 4040c0f59..e41e91796 100644 --- a/composer.lock +++ b/composer.lock @@ -2798,4 +2798,4 @@ "platform": {}, "platform-dev": {}, "plugin-api-version": "2.9.0" -} \ No newline at end of file +} From 70aae0f8811a23933a58cf380dee7fdb49545d6c Mon Sep 17 00:00:00 2001 From: Mauro Mura Date: Thu, 7 May 2026 10:37:40 +0200 Subject: [PATCH 53/57] Update composer command and commit logic --- .github/workflows/nmc-custom-oidc-composer.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nmc-custom-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml index c1b9d0a5d..71080bea7 100644 --- a/.github/workflows/nmc-custom-oidc-composer.yml +++ b/.github/workflows/nmc-custom-oidc-composer.yml @@ -117,17 +117,16 @@ jobs: web-token/jwt-encryption \ spomky-labs/aes-key-wrap \ --with-all-dependencies \ - --no-interaction + --no-interaction \ + --no-scripts - name: Commit and push composer changes if: steps.check_composer.outputs.files_exists == 'true' run: | - if git diff --quiet; then + if git diff --cached --quiet; then echo "No composer changes to commit" exit 0 fi - - // git add composer.json composer.lock lib/Vendor lib/autoload - + git commit -m "Add custom user_oidc composer dependencies" git push origin "HEAD:${ASSEMBLY_BRANCH}" From b35c9115735603a80316f00375cb88f83dec11ab Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 10:42:14 +0200 Subject: [PATCH 54/57] remove note --- lib/AppInfo/Application.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index abfa5c0fd..6958a6c4d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -38,8 +38,6 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; - -// this is needed only for the event-based provisioning solution use Psr\Container\ContainerInterface; use Throwable; From f42649169dedc2ef70931b06d96659c2f56a39e9 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 10:47:05 +0200 Subject: [PATCH 55/57] fix merge --- lib/AppInfo/Application.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d5a568aab..15c89a733 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -24,6 +24,7 @@ use OCA\UserOIDC\Service\ID4MeService; use OCA\UserOIDC\Service\RequestClassificationService; use OCA\UserOIDC\Service\SettingsService; +use OCA\UserOIDC\Service\TokenService; use OCA\UserOIDC\User\Backend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -85,6 +86,7 @@ public function register(IRegistrationContext $context): void { public function boot(IBootContext $context): void { $context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession'])); + // $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken'])); /** @var IUserSession $userSession */ $userSession = $this->getContainer()->get(IUserSession::class); if ($userSession->isLoggedIn()) { @@ -101,6 +103,10 @@ public function boot(IBootContext $context): void { } } + private function checkLoginToken(TokenService $tokenService): void { + $tokenService->checkLoginToken(); + } + /** * This is the automatic redirect exclusively for Nextcloud/Magentacloud clients, completely skipping consent layer. */ From 2ecbb9d503fa9da36bf6ce2ff167b74b39a3e9c9 Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 11:13:15 +0200 Subject: [PATCH 56/57] removed imports --- lib/AppInfo/Application.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 15c89a733..410ae026d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -33,11 +33,9 @@ use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; -use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\IUserSession; -use OCP\Security\ISecureRandom; use Throwable; class Application extends App implements IBootstrap { @@ -114,8 +112,8 @@ private function registerNmcClientFlow( IRequest $request, IURLGenerator $urlGenerator, ProviderMapper $providerMapper, - ISession $session, - ISecureRandom $random, + \OCP\ISession $session, + \OCP\Security\ISecureRandom $random, ): void { $providers = $this->getCachedProviders($providerMapper); @@ -137,7 +135,12 @@ private function registerNmcClientFlow( return; } - $stateToken = $random->generate(64, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); + $stateToken = $random->generate( + 64, + \OCP\Security\ISecureRandom::CHAR_LOWER + . \OCP\Security\ISecureRandom::CHAR_UPPER + . \OCP\Security\ISecureRandom::CHAR_DIGITS + ); $session->set('client.flow.state.token', $stateToken); From 365f51c1f6b4a10604756effb6bf60d9e9b52eec Mon Sep 17 00:00:00 2001 From: memurats Date: Thu, 7 May 2026 11:16:23 +0200 Subject: [PATCH 57/57] reduced version number --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 8685f1915..d6acbf761 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -8,7 +8,7 @@ OpenID Connect user backend

Use an OpenID Connect backend to login to your Nextcloud Allows flexible configuration of an OIDC server as Nextcloud login user backend. - 8.10.1 + 8.1.1 agpl Roeland Jago Douma Julius Härtl