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