diff --git a/.github/README.md b/.github/README.md
new file mode 100644
index 00000000..a942804d
--- /dev/null
+++ b/.github/README.md
@@ -0,0 +1,54 @@
+# 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)
+
+
+## Feature: 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
+
+## Feature: 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)
+# ALWAYS update web-token dependencies in composer.lock
+# to avoid upstream conflicts. The lock file diff should only contain adds to upstream state!
+composer update "web-token/jwt-*"
+```
+
+
+### Configuring an additional Bearer preshared secret with provider
+TODO
+
+### Testing Bearer secrets
+TODO
diff --git a/.github/workflows/nmc-custom-app-release.yml b/.github/workflows/nmc-custom-app-release.yml
new file mode 100644
index 00000000..64d287ce
--- /dev/null
+++ b/.github/workflows/nmc-custom-app-release.yml
@@ -0,0 +1,68 @@
+###
+# 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
+
+ composerdep:
+ strategy:
+ fail-fast: false
+ uses: ./.github/workflows/nmc-custom-oidc-composer.yml
+ needs: assemble-custom
+ with:
+ assembly: ${{ 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, composerdep ]
+ 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
diff --git a/.github/workflows/nmc-custom-app-versions.yml b/.github/workflows/nmc-custom-app-versions.yml
new file mode 100644
index 00000000..e18f1941
--- /dev/null
+++ b/.github/workflows/nmc-custom-app-versions.yml
@@ -0,0 +1,72 @@
+###
+# 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:
+
+ assemble:
+ strategy:
+ fail-fast: false
+ matrix:
+ custombase: [ "main" ]
+ 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
+
+ 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" ]
+ uses: nextmcloud/.github/.github/workflows/nmc-custom-app-phpunit.yml@master
+ needs: composerdep
+ 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-oidc-composer.yml b/.github/workflows/nmc-custom-oidc-composer.yml
new file mode 100644
index 00000000..d4f2c527
--- /dev/null
+++ b/.github/workflows/nmc-custom-oidc-composer.yml
@@ -0,0 +1,81 @@
+###
+# SPDX-License-Identifier: AGPL-3.0
+#
+# Author: Bernd Rederlechner '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' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'],
- ['name' => 'api#deleteUser', 'url' => '/user/{userId}', 'verb' => 'DELETE'],
+ // ['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'],
+ // ['name' => 'api#deleteUser', 'url' => '/user/{userId}', 'verb' => 'DELETE'],
['name' => 'id4me#showLogin', 'url' => '/id4me', 'verb' => 'GET'],
['name' => 'id4me#login', 'url' => '/id4me', 'verb' => 'POST'],
diff --git a/composer.json b/composer.json
index 438248e8..a100b2fc 100644
--- a/composer.json
+++ b/composer.json
@@ -31,7 +31,17 @@
"require": {
"id4me/id4me-rp": "^1.2",
"firebase/php-jwt": "^6.8.1",
- "bamarni/composer-bin-plugin": "^1.4"
+ "bamarni/composer-bin-plugin": "^1.4",
+ "web-token/jwt-core": "^2.0",
+ "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"
},
"require-dev": {
"nextcloud/coding-standard": "^1.0.0",
diff --git a/composer.lock b/composer.lock
index 0955e1d7..2ca3ee0a 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": "04286fb7b53817717fac1f09a0a61a40",
+ "content-hash": "bb379f676922e3656d7bc0ed16205df8",
"packages": [
{
"name": "bamarni/composer-bin-plugin",
@@ -63,6 +63,142 @@
},
"time": "2022-10-31T08:38:03+00:00"
},
+ {
+ "name": "brick/math",
+ "version": "0.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brick/math.git",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
+ "vimeo/psalm": "4.9.2"
+ },
+ "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",
+ "brick",
+ "math"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/brick/math",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-15T20:50:18+00:00"
+ },
+ {
+ "name": "fgrosse/phpasn1",
+ "version": "v2.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fgrosse/PHPASN1.git",
+ "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b",
+ "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "~2.0",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "suggest": {
+ "ext-bcmath": "BCmath is the fallback extension for big integer calculations",
+ "ext-curl": "For loading OID information from the web if they have not bee defined statically",
+ "ext-gmp": "GMP is the preferred extension for big integer calculations",
+ "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "FG\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Friedrich Große",
+ "email": "friedrich.grosse@gmail.com",
+ "homepage": "https://github.com/FGrosse",
+ "role": "Author"
+ },
+ {
+ "name": "All contributors",
+ "homepage": "https://github.com/FGrosse/PHPASN1/contributors"
+ }
+ ],
+ "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
+ "homepage": "https://github.com/FGrosse/PHPASN1",
+ "keywords": [
+ "DER",
+ "asn.1",
+ "asn1",
+ "ber",
+ "binary",
+ "decoding",
+ "encoding",
+ "x.509",
+ "x.690",
+ "x509",
+ "x690"
+ ],
+ "support": {
+ "issues": "https://github.com/fgrosse/PHPASN1/issues",
+ "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0"
+ },
+ "abandoned": true,
+ "time": "2022-12-19T11:08:26+00:00"
+ },
{
"name": "firebase/php-jwt",
"version": "v6.11.1",
@@ -279,6 +415,1106 @@
}
],
"time": "2024-12-14T21:03:54+00:00"
+ },
+ {
+ "name": "spomky-labs/aes-key-wrap",
+ "version": "v6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Spomky-Labs/aes-key-wrap.git",
+ "reference": "97388255a37ad6fb1ed332d07e61fa2b7bb62e0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Spomky-Labs/aes-key-wrap/zipball/97388255a37ad6fb1ed332d07e61fa2b7bb62e0d",
+ "reference": "97388255a37ad6fb1ed332d07e61fa2b7bb62e0d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "lib-openssl": "*",
+ "php": ">=7.2",
+ "thecodingmachine/safe": "^1.1"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.0",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-beberlei-assert": "^0.12",
+ "phpstan/phpstan-deprecation-rules": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpstan/phpstan-strict-rules": "^0.12",
+ "phpunit/phpunit": "^7.0|^8.0|^9.0",
+ "thecodingmachine/phpstan-safe-rule": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0.x-dev"
+ }
+ },
+ "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/v6.0.0"
+ },
+ "time": "2020-08-01T14:07:55+00:00"
+ },
+ {
+ "name": "spomky-labs/base64url",
+ "version": "v2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Spomky-Labs/base64url.git",
+ "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
+ "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.11|^0.12",
+ "phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
+ "phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
+ "phpstan/phpstan-phpunit": "^0.11|^0.12",
+ "phpstan/phpstan-strict-rules": "^0.11|^0.12"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Base64Url\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Florent Morselli",
+ "homepage": "https://github.com/Spomky-Labs/base64url/contributors"
+ }
+ ],
+ "description": "Base 64 URL Safe Encoding/Decoding PHP Library",
+ "homepage": "https://github.com/Spomky-Labs/base64url",
+ "keywords": [
+ "base64",
+ "rfc4648",
+ "safe",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/Spomky-Labs/base64url/issues",
+ "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spomky",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "time": "2020-11-03T09:10:25+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "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.33.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-12-23T08:48:59+00:00"
+ },
+ {
+ "name": "thecodingmachine/safe",
+ "version": "v1.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thecodingmachine/safe.git",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12",
+ "squizlabs/php_codesniffer": "^3.2",
+ "thecodingmachine/phpstan-strict-rules": "^0.12"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "deprecated/apc.php",
+ "deprecated/libevent.php",
+ "deprecated/mssql.php",
+ "deprecated/stats.php",
+ "lib/special_cases.php",
+ "generated/apache.php",
+ "generated/apcu.php",
+ "generated/array.php",
+ "generated/bzip2.php",
+ "generated/calendar.php",
+ "generated/classobj.php",
+ "generated/com.php",
+ "generated/cubrid.php",
+ "generated/curl.php",
+ "generated/datetime.php",
+ "generated/dir.php",
+ "generated/eio.php",
+ "generated/errorfunc.php",
+ "generated/exec.php",
+ "generated/fileinfo.php",
+ "generated/filesystem.php",
+ "generated/filter.php",
+ "generated/fpm.php",
+ "generated/ftp.php",
+ "generated/funchand.php",
+ "generated/gmp.php",
+ "generated/gnupg.php",
+ "generated/hash.php",
+ "generated/ibase.php",
+ "generated/ibmDb2.php",
+ "generated/iconv.php",
+ "generated/image.php",
+ "generated/imap.php",
+ "generated/info.php",
+ "generated/ingres-ii.php",
+ "generated/inotify.php",
+ "generated/json.php",
+ "generated/ldap.php",
+ "generated/libxml.php",
+ "generated/lzf.php",
+ "generated/mailparse.php",
+ "generated/mbstring.php",
+ "generated/misc.php",
+ "generated/msql.php",
+ "generated/mysql.php",
+ "generated/mysqli.php",
+ "generated/mysqlndMs.php",
+ "generated/mysqlndQc.php",
+ "generated/network.php",
+ "generated/oci8.php",
+ "generated/opcache.php",
+ "generated/openssl.php",
+ "generated/outcontrol.php",
+ "generated/password.php",
+ "generated/pcntl.php",
+ "generated/pcre.php",
+ "generated/pdf.php",
+ "generated/pgsql.php",
+ "generated/posix.php",
+ "generated/ps.php",
+ "generated/pspell.php",
+ "generated/readline.php",
+ "generated/rpminfo.php",
+ "generated/rrd.php",
+ "generated/sem.php",
+ "generated/session.php",
+ "generated/shmop.php",
+ "generated/simplexml.php",
+ "generated/sockets.php",
+ "generated/sodium.php",
+ "generated/solr.php",
+ "generated/spl.php",
+ "generated/sqlsrv.php",
+ "generated/ssdeep.php",
+ "generated/ssh2.php",
+ "generated/stream.php",
+ "generated/strings.php",
+ "generated/swoole.php",
+ "generated/uodbc.php",
+ "generated/uopz.php",
+ "generated/url.php",
+ "generated/var.php",
+ "generated/xdiff.php",
+ "generated/xml.php",
+ "generated/xmlrpc.php",
+ "generated/yaml.php",
+ "generated/yaz.php",
+ "generated/zip.php",
+ "generated/zlib.php"
+ ],
+ "psr-4": {
+ "Safe\\": [
+ "lib/",
+ "deprecated/",
+ "generated/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
+ "support": {
+ "issues": "https://github.com/thecodingmachine/safe/issues",
+ "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
+ },
+ "time": "2020-10-28T17:51:34+00:00"
+ },
+ {
+ "name": "web-token/jwt-core",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-core.git",
+ "reference": "53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-core/zipball/53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678",
+ "reference": "53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.17|^0.9",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "fgrosse/phpasn1": "^2.0",
+ "php": ">=7.2",
+ "spomky-labs/base64url": "^1.0|^2.0"
+ },
+ "conflict": {
+ "spomky-labs/jose": "*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Core\\": ""
+ }
+ },
+ "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": "Core component of the JWT Framework.",
+ "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/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-03-17T14:55:52+00:00"
+ },
+ {
+ "name": "web-token/jwt-encryption",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-encryption.git",
+ "reference": "3b8d67d7c5c013750703e7c27f1001544407bbb2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-encryption/zipball/3b8d67d7c5c013750703e7c27f1001544407bbb2",
+ "reference": "3b8d67d7c5c013750703e7c27f1001544407bbb2",
+ "shasum": ""
+ },
+ "require": {
+ "web-token/jwt-core": "^2.1"
+ },
+ "suggest": {
+ "web-token/jwt-encryption-algorithm-aescbc": "AES CBC Based Content Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-aesgcm": "AES GCM Based Content Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-aesgcmkw": "AES GCM Key Wrapping Based Key Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-aeskw": "AES Key Wrapping Based Key Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-dir": "Direct Key Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-ecdh-es": "ECDH-ES Based Key Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-experimental": "Experimental Key and Signature Algorithms",
+ "web-token/jwt-encryption-algorithm-pbes2": "PBES2 Based Key Encryption Algorithms",
+ "web-token/jwt-encryption-algorithm-rsa": "RSA Based Key Encryption Algorithms"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Encryption\\": ""
+ }
+ },
+ "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-encryption/contributors"
+ }
+ ],
+ "description": "Encryption component of the JWT Framework.",
+ "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/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-03-17T14:55:52+00:00"
+ },
+ {
+ "name": "web-token/jwt-encryption-algorithm-aescbc",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-encryption-algorithm-aescbc.git",
+ "reference": "0359b82b349c8bbc82c19ba0382e1a1b3f788421"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-encryption-algorithm-aescbc/zipball/0359b82b349c8bbc82c19ba0382e1a1b3f788421",
+ "reference": "0359b82b349c8bbc82c19ba0382e1a1b3f788421",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "web-token/jwt-encryption": "^2.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Encryption\\Algorithm\\ContentEncryption\\": ""
+ }
+ },
+ "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": "AES CBC Based Content Encryption Algorithms the JWT Framework.",
+ "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-algorithm-aescbc/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-01-21T19:18:03+00:00"
+ },
+ {
+ "name": "web-token/jwt-encryption-algorithm-ecdh-es",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-encryption-algorithm-ecdh-es.git",
+ "reference": "736c2c5a3997e4cfb84e9f8f63bf17b5d7ca4fd0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-encryption-algorithm-ecdh-es/zipball/736c2c5a3997e4cfb84e9f8f63bf17b5d7ca4fd0",
+ "reference": "736c2c5a3997e4cfb84e9f8f63bf17b5d7ca4fd0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "spomky-labs/aes-key-wrap": "^5.0|^6.0",
+ "web-token/jwt-encryption": "^2.1",
+ "web-token/jwt-util-ecc": "^2.1"
+ },
+ "suggest": {
+ "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Encryption\\Algorithm\\KeyEncryption\\": ""
+ }
+ },
+ "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": "ECDH-ES Based Key Encryption Algorithms the JWT Framework.",
+ "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-algorithm-ecdh-es/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-03-17T14:55:52+00:00"
+ },
+ {
+ "name": "web-token/jwt-encryption-algorithm-pbes2",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-encryption-algorithm-pbes2.git",
+ "reference": "d0294e7821d4a9b70454d3b13441add59c525275"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-encryption-algorithm-pbes2/zipball/d0294e7821d4a9b70454d3b13441add59c525275",
+ "reference": "d0294e7821d4a9b70454d3b13441add59c525275",
+ "shasum": ""
+ },
+ "require": {
+ "web-token/jwt-encryption": "^2.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Encryption\\Algorithm\\KeyEncryption\\": ""
+ }
+ },
+ "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": "PBES2* Based Key Encryption Algorithms the JWT Framework.",
+ "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-algorithm-pbes2/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-01-21T19:18:03+00:00"
+ },
+ {
+ "name": "web-token/jwt-encryption-algorithm-rsa",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-encryption-algorithm-rsa.git",
+ "reference": "2aab79c4cda093d2ee94756d0b1b46e93b380f55"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-encryption-algorithm-rsa/zipball/2aab79c4cda093d2ee94756d0b1b46e93b380f55",
+ "reference": "2aab79c4cda093d2ee94756d0b1b46e93b380f55",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.17|^0.9",
+ "ext-openssl": "*",
+ "symfony/polyfill-mbstring": "^1.12",
+ "web-token/jwt-encryption": "^2.1"
+ },
+ "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"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Encryption\\Algorithm\\KeyEncryption\\": ""
+ }
+ },
+ "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": "RSA Based Key Encryption Algorithms the JWT Framework.",
+ "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-algorithm-rsa/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-01-21T19:18:03+00:00"
+ },
+ {
+ "name": "web-token/jwt-signature",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-signature.git",
+ "reference": "015b59aaf3b6e8fb9f5bd1338845b7464c7d8103"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/015b59aaf3b6e8fb9f5bd1338845b7464c7d8103",
+ "reference": "015b59aaf3b6e8fb9f5bd1338845b7464c7d8103",
+ "shasum": ""
+ },
+ "require": {
+ "web-token/jwt-core": "^2.1"
+ },
+ "suggest": {
+ "web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms",
+ "web-token/jwt-signature-algorithm-eddsa": "EdDSA Based Signature Algorithms",
+ "web-token/jwt-signature-algorithm-experimental": "Experimental Signature Algorithms",
+ "web-token/jwt-signature-algorithm-hmac": "HMAC Based Signature Algorithms",
+ "web-token/jwt-signature-algorithm-none": "None Signature Algorithm",
+ "web-token/jwt-signature-algorithm-rsa": "RSA Based Signature Algorithms"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Signature\\": ""
+ }
+ },
+ "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-signature/contributors"
+ }
+ ],
+ "description": "Signature component of the JWT Framework.",
+ "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/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-03-01T19:55:28+00:00"
+ },
+ {
+ "name": "web-token/jwt-signature-algorithm-hmac",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-signature-algorithm-hmac.git",
+ "reference": "d208b1c50b408fa711bfeedeed9fb5d9be1d3080"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-signature-algorithm-hmac/zipball/d208b1c50b408fa711bfeedeed9fb5d9be1d3080",
+ "reference": "d208b1c50b408fa711bfeedeed9fb5d9be1d3080",
+ "shasum": ""
+ },
+ "require": {
+ "web-token/jwt-signature": "^2.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Signature\\Algorithm\\": ""
+ }
+ },
+ "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": "HMAC Based Signature Algorithms the JWT Framework.",
+ "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-algorithm-hmac/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-01-21T19:18:03+00:00"
+ },
+ {
+ "name": "web-token/jwt-signature-algorithm-rsa",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-signature-algorithm-rsa.git",
+ "reference": "513ad90eb5ef1886ff176727a769bda4618141b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-signature-algorithm-rsa/zipball/513ad90eb5ef1886ff176727a769bda4618141b0",
+ "reference": "513ad90eb5ef1886ff176727a769bda4618141b0",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.17|^0.9",
+ "ext-openssl": "*",
+ "web-token/jwt-signature": "^2.1"
+ },
+ "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"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Signature\\Algorithm\\": ""
+ }
+ },
+ "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": "RSA Based Signature Algorithms the JWT Framework.",
+ "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-algorithm-rsa/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-01-21T19:18:03+00:00"
+ },
+ {
+ "name": "web-token/jwt-util-ecc",
+ "version": "v2.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/web-token/jwt-util-ecc.git",
+ "reference": "915f3fde86f5236c205620d61177b9ef43863deb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/web-token/jwt-util-ecc/zipball/915f3fde86f5236c205620d61177b9ef43863deb",
+ "reference": "915f3fde86f5236c205620d61177b9ef43863deb",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8.17|^0.9"
+ },
+ "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"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jose\\Component\\Core\\Util\\Ecc\\": ""
+ }
+ },
+ "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": "ECC Tools for the JWT Framework.",
+ "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-util-ecc/tree/v2.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://www.patreon.com/FlorentMorselli",
+ "type": "patreon"
+ }
+ ],
+ "abandoned": "web-token/jwt-library",
+ "time": "2021-03-24T13:35:17+00:00"
}
],
"packages-dev": [
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 9842067f..de4272e2 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -20,7 +20,10 @@
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\ProvisioningEventService;
+use OCA\UserOIDC\Service\ProvisioningService;
use OCA\UserOIDC\Service\SettingsService;
use OCA\UserOIDC\Service\TokenService;
use OCA\UserOIDC\User\Backend;
@@ -31,9 +34,14 @@
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;
+
+// this is needed only for the special, shortened client login flow
+use Psr\Container\ContainerInterface;
use Throwable;
class Application extends App implements IBootstrap {
@@ -48,11 +56,19 @@ public function __construct(array $urlParams = []) {
}
public function register(IRegistrationContext $context): void {
+ // Register the composer autoloader required for the added jwt-token libs
+ include_once __DIR__ . '/../../vendor/autoload.php';
+
+ // override registration of provisioning srevice 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);
/* 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', '>=')) {
@@ -74,7 +90,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()) {
@@ -84,10 +100,73 @@ public function boot(IBootContext $context): void {
try {
$context->injectFn(\Closure::fromCallable([$this, 'registerRedirect']));
$context->injectFn(\Closure::fromCallable([$this, 'registerLogin']));
+
+ // this is the custom auto-redirect for MagentaCLOUD client access
+ $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow']));
} catch (Throwable $e) {
}
}
+ /**
+ * 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);
+
+ // Handle immediate redirect on client first-time login
+ $isClientLoginFlow = false;
+
+ try {
+ $isClientLoginFlow = $request->getPathInfo() === '/login/flow';
+ } catch (Exception $e) {
+ // in case any errors happen when checking for the path do not apply redirect logic as it is only needed for the login
+ }
+
+ if ($isClientLoginFlow) {
+ // only redirect if Telekom provider registered
+ $tproviders = array_values(array_filter($providers, function ($p) {
+ return strtolower($p->getIdentifier()) === 'telekom';
+ }));
+
+ if (count($tproviders) == 0) {
+ // always show normal login flow as error fallback
+ return;
+ }
+
+ $stateToken = $random->generate(64, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
+ $session->set('client.flow.state.token', $stateToken);
+
+ // call the service to get the params, but suppress the template
+ // compute grant redirect Url to go directly to Telekom login
+ $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [
+ 'stateToken' => $stateToken,
+ // grantPage service operation is deriving oauth2 client name (again),
+ // so we simply pass on clientIdentifier or empty string
+ 'clientIdentifier' => $request->getParam('clientIdentifier', ''),
+ 'direct' => $request->getParam('direct', '0')
+ ]);
+
+ if ($redirectUrl === null) {
+
+ // always show normal login flow as error fallback
+ return;
+ }
+
+ // direct login, consent layer later
+ $targetUrl = $urlGenerator->linkToRoute(self::APP_ID . '.login.login', [
+ 'providerId' => $tproviders[0]->getId(),
+ 'redirectUrl' => $redirectUrl
+ ]);
+
+ header('Location: ' . $targetUrl);
+ exit();
+ }
+ }
+
private function checkLoginToken(TokenService $tokenService): void {
$tokenService->checkLoginToken();
}
diff --git a/lib/Command/UpsertProvider.php b/lib/Command/UpsertProvider.php
index f6690046..825100e4 100644
--- a/lib/Command/UpsertProvider.php
+++ b/lib/Command/UpsertProvider.php
@@ -179,6 +179,7 @@ protected function configure() {
->addOption('clientid', 'c', InputOption::VALUE_REQUIRED, 'OpenID client identifier')
->addOption('clientsecret', 's', InputOption::VALUE_REQUIRED, '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"');
@@ -206,10 +207,17 @@ protected function execute(InputInterface $input, OutputInterface $output) {
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);
@@ -250,7 +258,7 @@ protected function execute(InputInterface $input, OutputInterface $output) {
}
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, '');
@@ -306,4 +314,8 @@ private function listProviders(InputInterface $input, OutputInterface $output) {
$table->render();
return 0;
}
+
+ private function base64UrlEncode(string $data): string {
+ return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+ }
}
diff --git a/lib/Controller/LoginController.php b/lib/Controller/LoginController.php
index da786660..dcc0873e 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;
@@ -171,12 +172,26 @@ public function login(int $providerId, ?string $redirectUrl = null) {
return $this->buildErrorTemplateResponse($message, Http::STATUS_NOT_FOUND, ['reason' => 'provider unreachable']);
}
- $state = $this->random->generate(32, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_UPPER);
- $this->session->set(self::STATE, $state);
- $this->session->set(self::REDIRECT_AFTER_LOGIN, $redirectUrl);
+ // $state = $this->random->generate(32, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_UPPER);
+ // $this->session->set(self::STATE, $state);
+ // $this->session->set(self::REDIRECT_AFTER_LOGIN, $redirectUrl);
- $nonce = $this->random->generate(32, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_UPPER);
- $this->session->set(self::NONCE, $nonce);
+ // $nonce = $this->random->generate(32, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_UPPER);
+ // $this->session->set(self::NONCE, $nonce);
+
+ // check if oidc state is present in session data
+ if ($this->session->exists(self::STATE)) {
+ $state = $this->session->get(self::STATE);
+ $nonce = $this->session->get(self::NONCE);
+ } else {
+ $state = $this->random->generate(32, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_UPPER);
+ $this->session->set(self::STATE, $state);
+ $this->session->set(self::REDIRECT_AFTER_LOGIN, $redirectUrl);
+
+ $nonce = $this->random->generate(32, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_UPPER);
+ $this->session->set(self::NONCE, $nonce);
+ $this->session->set(self::PROVIDERID, $providerId);
+ }
$oidcSystemConfig = $this->config->getSystemValue('user_oidc', []);
$isPkceSupported = in_array('S256', $discovery['code_challenge_methods_supported'] ?? [], true);
@@ -188,7 +203,7 @@ public function login(int $providerId, ?string $redirectUrl = null) {
$this->session->set(self::CODE_VERIFIER, $code_verifier);
}
- $this->session->set(self::PROVIDERID, $providerId);
+ // $this->session->set(self::PROVIDERID, $providerId);
$this->session->close();
// get attribute mapping settings
@@ -321,6 +336,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([
@@ -554,6 +574,24 @@ public function code(string $state = '', string $code = '', string $scope = '',
}
if ($autoProvisionAllowed) {
+ $user = null;
+
+ try {
+ // use potential user from other backend, create it in our backend if it does not exist
+ $user = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser);
+ } catch (ProvisioningDeniedException $denied) {
+ // TODO: MagentaCLOUD should upstream the exception handling
+ $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()]);
+ } else {
+ // error response is a redirect, e.g. to a booking site
+ // so that you can immediately get the registration page
+ return new RedirectResponse($redirectUrl);
+ }
+ }
+
if (!$softAutoProvisionAllowed && $existingUser !== null && $existingUser->getBackendClassName() !== Application::APP_ID) {
// if soft auto-provisioning is disabled,
// we refuse login for a user that already exists in another backend
@@ -601,16 +639,20 @@ public function code(string $state = '', string $code = '', string $scope = '',
$this->eventDispatcher->dispatchTyped(new UserLoggedInEvent($user, $user->getUID(), null, false));
}
- $storeLoginTokenEnabled = $this->appConfig->getValueString(Application::APP_ID, 'store_login_token', '0') === '1';
- if ($storeLoginTokenEnabled) {
+ // $storeLoginTokenEnabled = $this->appConfig->getValueString(Application::APP_ID, 'store_login_token', '0') === '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');
+ // $tokenData = array_merge(
+ // $data,
+ // ['provider_id' => $providerId],
+ // );
+ // $this->tokenService->storeToken($tokenData);
+ // }
+ // $this->config->setUserValue($user->getUID(), Application::APP_ID, 'had_token_once', '1');
+
+ // remove code login session values
+ $this->session->remove(self::STATE);
+ $this->session->remove(self::NONCE);
// Set last password confirm to the future as we don't have passwords to confirm against with SSO
$this->session->set('last-password-confirm', strtotime('+4 year', time()));
@@ -619,7 +661,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(),
@@ -897,10 +939,26 @@ private function getBackchannelLogoutErrorResponse(
'error' => $error,
'error_description' => $description,
],
- Http::STATUS_BAD_REQUEST,
+ Http::STATUS_OK,
);
}
+ /**
+ * Backward compatible function for MagentaCLOUD to smoothly transition to new config
+ *
+ * @PublicPage
+ * @NoCSRFRequired
+ * @BruteForceProtection(action=userOidcBackchannelLogout)
+ *
+ * @param string $logout_token
+ * @return JSONResponse
+ * @throws Exception
+ * @throws \JsonException
+ */
+ public function telekomBackChannelLogout(string $logout_token = '') {
+ return $this->backChannelLogout('Telekom', $logout_token);
+ }
+
private function toCodeChallenge(string $data): string {
// Basically one big work around for the base64url decode being weird
$h = pack('H*', hash('sha256', $data));
@@ -910,4 +968,20 @@ private function toCodeChallenge(string $data): string {
$s = str_replace('/', '_', $s); // 63rd char of encoding
return $s;
}
+
+ 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
+ }
}
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index d4d6b42d..319c55ba 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -77,7 +77,7 @@ public function isDiscoveryEndpointValid($url) {
}
#[PasswordConfirmationRequired]
- 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): JSONResponse {
if ($this->providerService->getProviderByIdentifier($identifier) !== null) {
@@ -102,6 +102,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);
@@ -110,7 +112,7 @@ public function createProvider(string $identifier, string $clientId, string $cli
}
#[PasswordConfirmationRequired]
- 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): JSONResponse {
$provider = $this->providerMapper->getProvider($providerId);
@@ -134,6 +136,9 @@ public function updateProvider(int $providerId, string $identifier, string $clie
$encryptedClientSecret = $this->crypto->encrypt($clientSecret);
$provider->setClientSecret($encryptedClientSecret);
}
+ if ($bearerSecret) {
+ $provider->setBearerSecret($this->base64UrlEncode($bearerSecret));
+ }
$provider->setDiscoveryEndpoint($discoveryEndpoint);
$provider->setEndSessionEndpoint($endSessionEndpoint ?: null);
$provider->setPostLogoutUri($postLogoutUri ?: null);
@@ -185,4 +190,8 @@ public function setAdminConfig(array $values): JSONResponse {
}
return new JSONResponse([]);
}
+
+ private function base64UrlEncode(string $data): string {
+ return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+ }
}
diff --git a/lib/Db/Provider.php b/lib/Db/Provider.php
index 838f10ff..6bf61ea8 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 getBearerSecret()
+ * @method void setBearerSecret(string $bearerSecret)
+ * @method string 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 d724436d..107d7101 100644
--- a/lib/Db/ProviderMapper.php
+++ b/lib/Db/ProviderMapper.php
@@ -81,6 +81,7 @@ public function getProviders() {
* @param string|null $clientid
* @param string|null $clientsecret
* @param string|null $discoveryuri
+ * @param string|null $bearersecret
* @param string $scope
* @param string|null $endsessionendpointuri
* @param string|null $postLogoutUri
@@ -90,7 +91,7 @@ public function getProviders() {
* @throws MultipleObjectsReturnedException
*/
public function createOrUpdateProvider(string $identifier, ?string $clientid = null,
- ?string $clientsecret = null, ?string $discoveryuri = null, string $scope = 'openid email profile',
+ ?string $clientsecret = null, ?string $discoveryuri = null, string $scope = 'openid email profile', ?string $bearersecret = null,
?string $endsessionendpointuri = null, ?string $postLogoutUri = null) {
try {
$provider = $this->findProviderByIdentifier($identifier);
@@ -109,6 +110,7 @@ public function createOrUpdateProvider(string $identifier, ?string $clientid = n
$provider->setDiscoveryEndpoint($discoveryuri);
$provider->setEndSessionEndpoint($endsessionendpointuri);
$provider->setPostLogoutUri($postLogoutUri);
+ $provider->setBearerSecret($bearersecret ?? '');
$provider->setScope($scope);
return $this->insert($provider);
} else {
@@ -127,6 +129,9 @@ public function createOrUpdateProvider(string $identifier, ?string $clientid = n
if ($postLogoutUri !== null) {
$provider->setPostLogoutUri($postLogoutUri ?: null);
}
+ if ($bearersecret !== null) {
+ $provider->setBearerSecret($bearersecret);
+ }
$provider->setScope($scope);
return $this->update($provider);
}
diff --git a/lib/Event/UserAccountChangeEvent.php b/lib/Event/UserAccountChangeEvent.php
new file mode 100644
index 00000000..de9fb5e5
--- /dev/null
+++ b/lib/Event/UserAccountChangeEvent.php
@@ -0,0 +1,125 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ */
+
+declare(strict_types=1);
+
+namespace OCA\UserOIDC\Event;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * Event to provide custom mapping logic based on the OIDC token data
+ * In order to avoid further processing the event propagation should be stopped
+ * in the listener after processing as the value might get overwritten afterwards
+ * by other listeners through $event->stopPropagation();
+ */
+class UserAccountChangeEvent extends Event {
+
+ /** @var string */
+ private $uid;
+
+ /** @var string|null */
+ private $displayname;
+
+ /** @var string|null */
+ private $mainEmail;
+
+ /** @var string|null */
+ private $quota;
+
+ /** @var object */
+ private $claims;
+
+ /** @var UserAccountChangeResult */
+ private $result;
+
+ public function __construct(
+ string $uid,
+ ?string $displayname,
+ ?string $mainEmail,
+ ?string $quota,
+ object $claims,
+ bool $accessAllowed = false
+ ) {
+ parent::__construct();
+ $this->uid = $uid;
+ $this->displayname = $displayname;
+ $this->mainEmail = $mainEmail;
+ $this->quota = $quota;
+ $this->claims = $claims;
+ $this->result = new UserAccountChangeResult($accessAllowed, 'default');
+ }
+
+ /**
+ * Get the user ID (UID) associated with the event.
+ *
+ * @return string
+ */
+ public function getUid(): string {
+ return $this->uid;
+ }
+
+ /**
+ * Get the display name for the account.
+ *
+ * @return string|null
+ */
+ public function getDisplayName(): ?string {
+ return $this->displayname;
+ }
+
+ /**
+ * Get the primary email address.
+ *
+ * @return string|null
+ */
+ public function getMainEmail(): ?string {
+ return $this->mainEmail;
+ }
+
+ /**
+ * Get the quota assigned to the account.
+ *
+ * @return string|null
+ */
+ public function getQuota(): ?string {
+ return $this->quota;
+ }
+
+ /**
+ * Get the OIDC claims associated with the event.
+ *
+ * @return object
+ */
+ public function getClaims(): object {
+ return $this->claims;
+ }
+
+ /**
+ * Get the current result object.
+ *
+ * @return UserAccountChangeResult
+ */
+ public function getResult(): UserAccountChangeResult {
+ return $this->result;
+ }
+
+ /**
+ * Replace the result object with a new one.
+ *
+ * @param bool $accessAllowed Whether access should be allowed
+ * @param string $reason Optional reason for the decision
+ * @param string|null $redirectUrl Optional redirect URL
+ * @return void
+ */
+ 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 00000000..1b19d639
--- /dev/null
+++ b/lib/Event/UserAccountChangeResult.php
@@ -0,0 +1,92 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ */
+
+declare(strict_types=1);
+
+namespace OCA\UserOIDC\Event;
+
+/**
+ * Represents the result of an account change event decision.
+ * Used to signal whether access is allowed and optional redirect/reason info.
+ */
+class UserAccountChangeResult {
+
+ /** @var bool */
+ private $accessAllowed;
+
+ /** @var string */
+ private $reason;
+
+ /** @var string|null */
+ private $redirectUrl;
+
+ public function __construct(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null) {
+ $this->accessAllowed = $accessAllowed;
+ $this->redirectUrl = $redirectUrl;
+ $this->reason = $reason;
+ }
+
+ /**
+ * Whether access for this user is allowed.
+ *
+ * @return bool
+ */
+ public function isAccessAllowed(): bool {
+ return $this->accessAllowed;
+ }
+
+ /**
+ * Set whether access for this user is allowed.
+ *
+ * @param bool $accessAllowed
+ * @return void
+ */
+ public function setAccessAllowed(bool $accessAllowed): void {
+ $this->accessAllowed = $accessAllowed;
+ }
+
+ /**
+ * Returns the optional alternate redirect URL.
+ *
+ * @return string|null
+ */
+ public function getRedirectUrl(): ?string {
+ return $this->redirectUrl;
+ }
+
+ /**
+ * Sets the optional alternate redirect URL.
+ *
+ * @param string|null $redirectUrl
+ * @return void
+ */
+ public function setRedirectUrl(?string $redirectUrl): void {
+ $this->redirectUrl = $redirectUrl;
+ }
+
+ /**
+ * Returns the decision reason.
+ *
+ * @return string
+ */
+ public function getReason(): string {
+ return $this->reason;
+ }
+
+ /**
+ * Sets the decision reason.
+ *
+ * @param string $reason
+ * @return void
+ */
+ public function setReason(string $reason): void {
+ $this->reason = $reason;
+ }
+}
diff --git a/lib/Exception/AttributeValueException.php b/lib/Exception/AttributeValueException.php
new file mode 100644
index 00000000..a4c80de4
--- /dev/null
+++ b/lib/Exception/AttributeValueException.php
@@ -0,0 +1,32 @@
+error;
+ }
+
+ public function getErrorDescription(): ?string {
+ return $this->errorDescription;
+ }
+}
diff --git a/lib/MagentaBearer/InvalidTokenException.php b/lib/MagentaBearer/InvalidTokenException.php
new file mode 100644
index 00000000..af97581b
--- /dev/null
+++ b/lib/MagentaBearer/InvalidTokenException.php
@@ -0,0 +1,8 @@
+
+ *
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\UserOIDC\MagentaBearer;
+
+use OCA\UserOIDC\AppInfo\Application;
+use OCA\UserOIDC\Db\Provider;
+use OCA\UserOIDC\Db\ProviderMapper;
+use OCA\UserOIDC\Db\UserMapper;
+use OCA\UserOIDC\Event\TokenValidatedEvent;
+use OCA\UserOIDC\Service\DiscoveryService;
+use OCA\UserOIDC\Service\ProviderService;
+use OCA\UserOIDC\Service\ProvisioningDeniedException;
+use OCA\UserOIDC\Service\ProvisioningEventService;
+use OCA\UserOIDC\User\AbstractOidcBackend;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
+use OCP\Security\ICrypto;
+use Psr\Log\LoggerInterface;
+
+class MBackend extends AbstractOidcBackend {
+
+ /**
+ * @var TokenService
+ */
+ protected $mtokenService;
+
+ /**
+ * @var ProvisioningEventService
+ */
+ protected $provisioningService;
+
+ /**
+ * @var ICrypto
+ */
+ protected $crypto;
+
+ public function __construct(IConfig $config,
+ UserMapper $userMapper,
+ LoggerInterface $logger,
+ IRequest $request,
+ ISession $session,
+ IURLGenerator $urlGenerator,
+ IEventDispatcher $eventDispatcher,
+ DiscoveryService $discoveryService,
+ ProviderMapper $providerMapper,
+ ProviderService $providerService,
+ IUserManager $userManager,
+ ICrypto $crypto,
+ TokenService $mtokenService,
+ ProvisioningEventService $provisioningService,
+ ) {
+ parent::__construct($config, $userMapper, $logger, $request, $session,
+ $urlGenerator, $eventDispatcher, $discoveryService,
+ $providerMapper, $providerService, $userManager);
+
+ $this->mtokenService = $mtokenService;
+ $this->provisioningService = $provisioningService;
+ $this->crypto = $crypto;
+ }
+
+ public function getBackendName(): string {
+ return Application::APP_ID . '\\MagentaBearer';
+ }
+
+ /**
+ * Backend is activated if header bearer token is detected.
+ *
+ * @return bool ture if bearer header found
+ */
+ public function isSessionActive(): bool {
+ // if this returns true, getCurrentUserId is called
+ // not sure if we should rather to the validation in here as otherwise it might fail for other backends or bave other side effects
+ $headerToken = $this->request->getHeader(Application::OIDC_API_REQ_HEADER);
+ // session is active if we have a bearer token (API request) OR if we logged in via user_oidc (we have a provider ID in the session)
+ return (preg_match('/^\s*bearer\s+/i', $headerToken) != false);
+ }
+
+ /**
+ * Return the id of the current user
+ * @return string
+ */
+ public function getCurrentUserId(): string {
+ // get the bearer token from headers
+ $headerToken = $this->request->getHeader(Application::OIDC_API_REQ_HEADER);
+ $headerToken = preg_replace('/^bearer\s+/i', '', $headerToken);
+ if ($headerToken === '') {
+ $this->logger->debug('No Bearer token');
+ return '';
+ }
+
+ $providers = $this->providerMapper->getProviders();
+ if (count($providers) === 0) {
+ $this->logger->debug('no OIDC providers');
+ return '';
+ }
+
+ // we implement only Telekom behavior (which includes auto-provisioning)
+ // so we neglect switches from the upstream Nexrcloud oidc handling
+
+ // try to validate with all providers
+ foreach ($providers as $provider) {
+ if ($this->providerService->getSetting($provider->getId(), ProviderService::SETTING_CHECK_BEARER, '0') === '1') {
+ 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 $eToken) {
+ // there is
+ $this->logger->debug('Invalid token:' . $eToken->getMessage() . '. Trying another provider.');
+ continue;
+ } catch (SignatureException $eSignature) {
+ // only the key seems not to fit, so try the next provider
+ $this->logger->debug($eSignature->getMessage() . '. Trying another provider.');
+ continue;
+ } catch (\Throwable $e) {
+ // there is
+ $this->logger->debug('General non matching provider problem:' . $e->getMessage());
+ continue;
+ }
+
+ $uidAttribute = $this->providerService->getSetting($provider->getId(), ProviderService::SETTING_MAPPING_UID, 'sub');
+ $userId = $payload->{$uidAttribute};
+ if ($userId === null) {
+ $this->logger->debug('No extractable user id, check mapping!');
+ return '';
+ }
+
+ // check bearercache here, not skipping validation for security reasons
+
+ // Telekom bearer does not support refersh_token, so the pupose of TokenValidatedEvent is not given,
+ // but could produce trouble if not send with the field, apart from performance aspects.
+ //
+ // $discovery = $this->discoveryService->obtainDiscovery($provider);
+ // $this->eventDispatcher->dispatchTyped(new TokenValidatedEvent(['token' => $payload], $provider, $discovery));
+
+ try {
+ $this->provisioningService->provisionUser($userId, $provider->getId(), $payload);
+ $this->checkFirstLogin($userId); // create the folders same as on web login
+ return $userId;
+ } catch (ProvisioningDeniedException $denied) {
+ $this->logger->error('Bearer token access denied: ' . $denied->getMessage());
+ return '';
+ }
+ }
+ }
+
+ $this->logger->debug('Could not find provider for token');
+ return '';
+ }
+
+ /**
+ * FIXXME: send proper error status from BAckend errors
+ *
+ * This function sets an https status code here (early in the failing backend operation)
+ * to pass on bearer errors cleanly with correct status code and a readable reason
+ *
+ * For this, there is a "tricky" setting of a header needed to make it working in all
+ * known situations, see
+ * https://stackoverflow.com/questions/3258634/php-how-to-send-http-response-code
+ */
+ // protected function sendHttpStatus(int $httpStatusCode, string $httpStatusMsg) {
+ // $phpSapiName = substr(php_sapi_name(), 0, 3);
+ // if ($phpSapiName == 'cgi' || $phpSapiName == 'fpm') {
+ // header('Status: ' . $httpStatusCode . ' ' . $httpStatusMsg);
+ // } else {
+ // $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
+ // header($protocol . ' ' . $httpStatusCode . ' ' . $httpStatusMsg);
+ // }
+ // }
+}
diff --git a/lib/MagentaBearer/SignatureException.php b/lib/MagentaBearer/SignatureException.php
new file mode 100644
index 00000000..ef04a4e0
--- /dev/null
+++ b/lib/MagentaBearer/SignatureException.php
@@ -0,0 +1,6 @@
+
+ *
+ * @author Bernd Rederlechner
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+declare(strict_types=1);
+
+namespace OCA\UserOIDC\MagentaBearer;
+
+use Jose\Component\Core\Algorithm;
+use Jose\Component\Core\AlgorithmManager;
+
+use Jose\Component\Core\JWK;
+use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
+use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW;
+
+use Jose\Component\Encryption\Algorithm\KeyEncryption\PBES2HS512A256KW;
+use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256;
+use Jose\Component\Encryption\Compression\CompressionMethodManager;
+
+use Jose\Component\Encryption\Compression\Deflate;
+use Jose\Component\Encryption\JWEDecrypter;
+use Jose\Component\Signature\Algorithm\HS256;
+use Jose\Component\Signature\Algorithm\HS384;
+
+use Jose\Component\Signature\Algorithm\HS512;
+use Jose\Component\Signature\JWS;
+use Jose\Component\Signature\JWSVerifier;
+use OCP\AppFramework\Utility\ITimeFactory;
+use Psr\Log\LoggerInterface;
+
+class TokenService {
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var ITimeFactory */
+ private $timeFactory;
+
+ /** @var DiscoveryService */
+ private $discoveryService;
+
+ public function __construct(LoggerInterface $logger,
+ ITimeFactory $timeFactory) {
+ $this->logger = $logger;
+ $this->timeFactory = $timeFactory;
+
+ // The key encryption algorithm manager with the A256KW algorithm.
+ $keyEncryptionAlgorithmManager = new AlgorithmManager([
+ new PBES2HS512A256KW(),
+ new RSAOAEP256(),
+ new ECDHESA256KW() ]);
+
+ // The content encryption algorithm manager with the A256CBC-HS256 algorithm.
+ $contentEncryptionAlgorithmManager = new AlgorithmManager([
+ new A256CBCHS512(),
+ ]);
+
+ // The compression method manager with the DEF (Deflate) method.
+ $compressionMethodManager = new CompressionMethodManager([
+ new Deflate(),
+ ]);
+
+ $signatureAlgorithmManager = new AlgorithmManager([
+ new HS256(),
+ new HS384(),
+ new HS512(),
+ ]);
+
+ // We instantiate our JWE Decrypter.
+ $this->jweDecrypter = new JWEDecrypter(
+ $keyEncryptionAlgorithmManager,
+ $contentEncryptionAlgorithmManager,
+ $compressionMethodManager
+ );
+
+ // We try to load the token.
+ $this->encryptionSerializerManager = new \Jose\Component\Encryption\Serializer\JWESerializerManager([
+ new \Jose\Component\Encryption\Serializer\CompactSerializer(),
+ ]);
+
+
+ // We instantiate our JWS Verifier.
+ $this->jwsVerifier = new JWSVerifier(
+ $signatureAlgorithmManager
+ );
+
+ // The serializer manager. We only use the JWE Compact Serialization Mode.
+ $this->serializerManager = new \Jose\Component\Signature\Serializer\JWSSerializerManager([
+ new \Jose\Component\Signature\Serializer\CompactSerializer(),
+ ]);
+ }
+
+ /**
+ * Implement JOSE decryption for SAM3 tokens
+ */
+ public function decryptToken(string $rawToken, string $decryptKey) : JWS {
+
+ // web-token library does not like underscores in headers, so replace them with - (which is valid in JWT)
+ $numSegments = substr_count($rawToken, '.') + 1;
+ $this->logger->debug('Bearer access token(segments=' . $numSegments . ')=' . $rawToken);
+ if ($numSegments > 3) {
+ // trusted authenticator and myself share the client secret,
+ // so use it is used for encrypted web tokens
+ $clientSecret = new JWK([
+ 'kty' => 'oct',
+ 'k' => $decryptKey
+ ]);
+
+ $jwe = $this->encryptionSerializerManager->unserialize($rawToken);
+
+ // We decrypt the token. This method does NOT check the header.
+ if ($this->jweDecrypter->decryptUsingKey($jwe, $clientSecret, 0)) {
+ return $this->serializerManager->unserialize($jwe->getPayload());
+ } else {
+ throw new InvalidTokenException('Unknown bearer encryption format');
+ }
+ } else {
+ return $this->serializerManager->unserialize($rawToken);
+ }
+ }
+
+ /**
+ * Get claims (even before verification to access e.g. aud standard field ...)
+ * Transform them in a format compatible with id_token representation.
+ */
+ public function decode(JWS $decodedToken) : object {
+ $this->logger->debug('Telekom SAM3 access token: ' . $decodedToken->getPayload());
+ $samContent = json_decode($decodedToken->getPayload(), false);
+
+ // remap all the custom claims
+ // adapt into OpenId id_token format (as far as possible)
+ $claimArray = $samContent->{'urn:telekom.com:idm:at:attributes'};
+ foreach ($claimArray as $claimKeyValue) {
+ $samContent->{'urn:telekom.com:' . $claimKeyValue->name} = $claimKeyValue->value;
+ }
+ unset($samContent->{'urn:telekom.com:idm:at:attributes'});
+
+ $this->logger->debug('Adapted OpenID-like token; ' . json_encode($samContent));
+ return $samContent;
+ }
+
+
+ public function verifySignature(JWS $decodedToken, string $signKey) {
+ $accessSecret = new JWK([
+ 'kty' => 'oct',
+ 'k' => $signKey
+ ]); // TODO: take the additional access key secret from settings
+
+ if (!$this->jwsVerifier->verifyWithKey($decodedToken, $accessSecret, 0)) {
+ throw new SignatureException('Invalid Signature');
+ }
+ }
+
+ public function verifyClaims(object $claims, array $audiences = [], int $leeway = 60) {
+ $timestamp = $this->timeFactory->getTime();
+
+ // Check the nbf if it is defined. This is the time that the
+ // token can actually be used. If it's not yet that time, abort.
+ if (isset($claims->nbf) && $claims->nbf > ($timestamp + $leeway)) {
+ throw new InvalidTokenException(
+ 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $claims->nbf)
+ );
+ }
+
+ // Check that this token has been created before 'now'. This prevents
+ // using tokens that have been created for later use (and haven't
+ // correctly used the nbf claim).
+ if (isset($claims->iat) && $claims->iat > ($timestamp + $leeway)) {
+ throw new InvalidTokenException(
+ 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $claims->iat)
+ );
+ }
+
+ // Check if this token has expired.
+ if (isset($claims->exp) && ($timestamp - $leeway) >= $claims->exp) {
+ throw new InvalidTokenException('Expired token');
+ }
+
+ // Check target audience (if given)
+ // Check if this token has expired.
+ if (empty(array_intersect($claims->aud, $audiences))) {
+ throw new InvalidTokenException('No acceptable audience in token.');
+ }
+ }
+}
diff --git a/lib/Migration/Version00008Date20211114183344.php b/lib/Migration/Version00008Date20211114183344.php
new file mode 100644
index 00000000..1c9cf6ea
--- /dev/null
+++ b/lib/Migration/Version00008Date20211114183344.php
@@ -0,0 +1,25 @@
+getTable('user_oidc_providers');
+ $table->addColumn('bearer_secret', 'string', [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+
+ return $schema;
+ }
+}
diff --git a/lib/Migration/Version010304Date20230902125945.php b/lib/Migration/Version010304Date20230902125945.php
new file mode 100644
index 00000000..bbc04849
--- /dev/null
+++ b/lib/Migration/Version010304Date20230902125945.php
@@ -0,0 +1,97 @@
+
+ *
+ * @author B. Rederlechner
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+namespace OCA\UserOIDC\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+use OCP\Security\ICrypto;
+
+class Version010304Date20230902125945 extends SimpleMigrationStep {
+
+ /**
+ * @var IDBConnection
+ */
+ private $connection;
+ /**
+ * @var ICrypto
+ */
+ private $crypto;
+
+ public function __construct(
+ IDBConnection $connection,
+ ICrypto $crypto,
+ ) {
+ $this->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/lib/Service/ProvisioningDeniedException.php b/lib/Service/ProvisioningDeniedException.php
new file mode 100644
index 00000000..9f317170
--- /dev/null
+++ b/lib/Service/ProvisioningDeniedException.php
@@ -0,0 +1,69 @@
+
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+namespace OCA\UserOIDC\Service;
+
+/**
+ * Exception if the precondition of the config update method isn't met
+ * @since 1.4.0
+ */
+class ProvisioningDeniedException extends \Exception {
+ private $redirectUrl;
+
+ /**
+ * Exception constructor including an option redirect url.
+ *
+ * @param string $message The error message. It will be not revealed to the
+ * the user (unless the hint is empty) and thus
+ * should be not translated.
+ * @param string $hint A useful message that is presented to the end
+ * user. It should be translated, but must not
+ * contain sensitive data.
+ * @param int $code Set default to 403 (Forbidden)
+ * @param \Exception|null $previous
+ */
+ public function __construct(string $message, ?string $redirectUrl = null, int $code = 403, ?\Exception $previous = null) {
+ parent::__construct($message, $code, $previous);
+ $this->redirectUrl = $redirectUrl;
+ }
+
+ /**
+ * Read optional failure redirect if available
+ * @return string|null
+ */
+ public function getRedirectUrl(): ?string {
+ return $this->redirectUrl;
+ }
+
+ /**
+ * Include redirect in string serialisation.
+ *
+ * @return string
+ */
+ public function __toString(): string {
+ $redirect = $this->redirectUrl ?? '';
+ return __CLASS__ . ": [{$this->code}]: {$this->message} ({$redirect})\n";
+ }
+}
diff --git a/lib/Service/ProvisioningEventService.php b/lib/Service/ProvisioningEventService.php
new file mode 100644
index 00000000..abcaf940
--- /dev/null
+++ b/lib/Service/ProvisioningEventService.php
@@ -0,0 +1,184 @@
+
+ *
+ * @author Bernd Rederlechner
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
+namespace OCA\UserOIDC\Service;
+
+use OCA\UserOIDC\Db\UserMapper;
+use OCA\UserOIDC\Event\AttributeMappedEvent;
+use OCA\UserOIDC\Event\UserAccountChangeEvent;
+use OCA\UserOIDC\Exception\AttributeValueException;
+use OCP\Accounts\IAccountManager;
+use OCP\DB\Exception;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Http\Client\IClientService;
+use OCP\IAvatarManager;
+use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\ISession;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\NotFoundExceptionInterface;
+use Psr\Log\LoggerInterface;
+
+// FIXME there should be an interface for both variations
+class ProvisioningEventService extends ProvisioningService {
+
+ /** @var IEventDispatcher */
+ private $eventDispatcher;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var ProviderService */
+ private $providerService;
+
+ public function __construct(
+ LocalIdService $idService,
+ ProviderService $providerService,
+ UserMapper $userMapper,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ IEventDispatcher $eventDispatcher,
+ LoggerInterface $logger,
+ IAccountManager $accountManager,
+ IClientService $clientService,
+ IAvatarManager $avatarManager,
+ IConfig $config,
+ ISession $session,
+ IFactory $l10nFactory,
+ ) {
+ parent::__construct($idService,
+ $providerService,
+ $userMapper,
+ $userManager,
+ $groupManager,
+ $eventDispatcher,
+ $logger,
+ $accountManager,
+ $clientService,
+ $avatarManager,
+ $config,
+ $session,
+ $l10nFactory,
+ );
+ $this->eventDispatcher = $eventDispatcher;
+ $this->logger = $logger;
+ $this->userManager = $userManager;
+ $this->providerService = $providerService;
+ }
+
+ protected function mapDispatchUID(int $providerid, object $payload, string $tokenUserId) {
+ $uidAttribute = $this->providerService->getSetting($providerid, ProviderService::SETTING_MAPPING_UID, 'sub');
+ $mappedUserId = $payload->{$uidAttribute} ?? $tokenUserId;
+ $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_UID, $payload, $mappedUserId);
+ $this->eventDispatcher->dispatchTyped($event);
+ return $event->getValue();
+ }
+
+ protected function mapDispatchDisplayname(int $providerid, object $payload) {
+ $displaynameAttribute = $this->providerService->getSetting($providerid, ProviderService::SETTING_MAPPING_DISPLAYNAME, 'displayname');
+ $mappedDisplayName = $payload->{$displaynameAttribute} ?? null;
+
+ if (isset($mappedDisplayName)) {
+ $limitedDisplayName = mb_substr($mappedDisplayName, 0, 255);
+ $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_DISPLAYNAME, $payload, $limitedDisplayName);
+ } else {
+ $event = new AttributeMappedEvent(ProviderService::SETTING_MAPPING_DISPLAYNAME, $payload);
+ }
+ $this->eventDispatcher->dispatchTyped($event);
+ return $event->getValue();
+ }
+
+ protected function mapDispatchEmail(int $providerid, object $payload) {
+ $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);
+ return $event->getValue();
+ }
+
+ protected function mapDispatchQuota(int $providerid, object $payload) {
+ $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);
+ return $event->getValue();
+ }
+
+ protected function dispatchUserAccountUpdate(string $uid, ?string $displayName, ?string $email, ?string $quota, object $payload) {
+ $event = new UserAccountChangeEvent($uid, $displayName, $email, $quota, $payload);
+ $this->eventDispatcher->dispatchTyped($event);
+ return $event->getResult();
+ }
+
+ /**
+ * Trigger a provisioning via event system.
+ * This allows to flexibly implement complex provisioning strategies -
+ * even in a separate app.
+ *
+ * On error, the provisioning logic can deliver failure reasons and
+ * even a redirect to a different endpoint.
+ *
+ * @param string $tokenUserId
+ * @param int $providerId
+ * @param object $idTokenPayload
+ * @param IUser|null $existingLocalUser
+ * @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 = $tokenUserId;
+ $displayname = $this->mapDispatchDisplayname($providerId, $idTokenPayload);
+ $email = $this->mapDispatchEmail($providerId, $idTokenPayload);
+ $quota = $this->mapDispatchQuota($providerId, $idTokenPayload);
+ } catch (AttributeValueException $eAttribute) {
+ $this->logger->info("{$tokenUserId}: user rejected by OpenId web authorization, reason: " . $eAttribute->getMessage());
+ throw new ProvisioningDeniedException($eAttribute->getMessage());
+ }
+
+ $userReaction = $this->dispatchUserAccountUpdate($uid, $displayname, $email, $quota, $idTokenPayload);
+
+ if ($userReaction->isAccessAllowed()) {
+ $this->logger->info("{$uid}: account accepted, reason: " . $userReaction->getReason());
+ $user = $this->userManager->get($uid);
+ return [
+ 'user' => $user,
+ 'userData' => get_object_vars($idTokenPayload), // optional, analog zu ProvisioningService
+ ];
+ } else {
+ $this->logger->info("{$uid}: account rejected, reason: " . $userReaction->getReason());
+ throw new ProvisioningDeniedException($userReaction->getReason(), $userReaction->getRedirectUrl());
+ }
+ }
+}
diff --git a/lib/User/AbstractOidcBackend.php b/lib/User/AbstractOidcBackend.php
new file mode 100644
index 00000000..d1e5de7b
--- /dev/null
+++ b/lib/User/AbstractOidcBackend.php
@@ -0,0 +1,220 @@
+
+ *
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\UserOIDC\User;
+
+use OCA\UserOIDC\Db\ProviderMapper;
+use OCA\UserOIDC\Db\UserMapper;
+use OCA\UserOIDC\Service\DiscoveryService;
+use OCA\UserOIDC\Service\ProviderService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\Authentication\IApacheBackend;
+use OCP\DB\Exception;
+use OCP\EventDispatcher\GenericEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\NotPermittedException;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\User\Backend\ABackend;
+use OCP\User\Backend\ICustomLogout;
+use OCP\User\Backend\IGetDisplayNameBackend;
+use OCP\User\Backend\IPasswordConfirmationBackend;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Introduce a baseclass to derive multiple backend from depending on
+ * the required bearer behavior.
+ *
+ * The class contains the OIDC part without the bearer aspects.
+ *
+ * FIXME: we should derive also the previous standard bearer backend from
+ * this class
+ */
+abstract class AbstractOidcBackend extends ABackend implements IPasswordConfirmationBackend, IGetDisplayNameBackend, IApacheBackend, ICustomLogout {
+
+ /** @var UserMapper */
+ protected $userMapper;
+ /** @var LoggerInterface */
+ protected $logger;
+ /** @var IRequest */
+ protected $request;
+ /** @var ProviderMapper */
+ protected $providerMapper;
+ /**
+ * @var ProviderService
+ */
+ protected $providerService;
+ /**
+ * @var IConfig
+ */
+ protected $config;
+ /**
+ * @var IEventDispatcher
+ */
+ protected $eventDispatcher;
+ /**
+ * @var DiscoveryService
+ */
+ protected $discoveryService;
+ /**
+ * @var IURLGenerator
+ */
+ protected $urlGenerator;
+ /**
+ * @var ISession
+ */
+ protected $session;
+ /**
+ * @var IUserManager
+ */
+ protected $userManager;
+
+ public function __construct(IConfig $config,
+ UserMapper $userMapper,
+ LoggerInterface $logger,
+ IRequest $request,
+ ISession $session,
+ IURLGenerator $urlGenerator,
+ IEventDispatcher $eventDispatcher,
+ DiscoveryService $discoveryService,
+ ProviderMapper $providerMapper,
+ ProviderService $providerService,
+ IUserManager $userManager) {
+ $this->config = $config;
+ $this->userMapper = $userMapper;
+ $this->logger = $logger;
+ $this->request = $request;
+ $this->providerMapper = $providerMapper;
+ $this->providerService = $providerService;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->discoveryService = $discoveryService;
+ $this->session = $session;
+ $this->urlGenerator = $urlGenerator;
+ $this->userManager = $userManager;
+ }
+
+ public function deleteUser($uid): bool {
+ try {
+ $user = $this->userMapper->getUser($uid);
+ $this->userMapper->delete($user);
+ return true;
+ } catch (Exception $e) {
+ $this->logger->error('Failed to delete user', [ 'exception' => $e ]);
+ return false;
+ }
+ }
+
+ public function getUsers($search = '', $limit = null, $offset = null) {
+ return array_map(function ($user) {
+ return $user->getUserId();
+ }, $this->userMapper->find($search, $limit, $offset));
+ }
+
+ public function userExists($uid): bool {
+ return $this->userMapper->userExists($uid);
+ }
+
+ public function getDisplayName($uid): string {
+ try {
+ $user = $this->userMapper->getUser($uid);
+ } catch (DoesNotExistException $e) {
+ return $uid;
+ }
+
+ return $user->getDisplayName();
+ }
+
+ public function getDisplayNames($search = '', $limit = null, $offset = null): array {
+ return $this->userMapper->findDisplayNames($search, $limit, $offset);
+ }
+
+ public function hasUserListings(): bool {
+ return true;
+ }
+
+ public function canConfirmPassword(string $uid): bool {
+ return false;
+ }
+
+ /**
+ * As session cannot be injected in the constructor here, we inject it later
+ *
+ * @param ISession $session
+ * @return void
+ */
+ public function injectSession(ISession $session): void {
+ $this->session = $session;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLogoutUrl(): string {
+ return $this->urlGenerator->linkToRouteAbsolute(
+ 'user_oidc.login.singleLogoutService',
+ [
+ 'requesttoken' => \OC::$server->getCsrfTokenManager()->getToken()->getEncryptedValue(),
+ ]
+ );
+ }
+
+
+ /**
+ * Inspired by lib/private/User/Session.php::prepareUserLogin()
+ *
+ * @param string $userId
+ * @return bool
+ * @throws NotFoundException
+ */
+ protected function checkFirstLogin(string $userId): bool {
+ $user = $this->userManager->get($userId);
+
+ if ($user === null) {
+ return false;
+ }
+
+ $firstLogin = $user->getLastLogin() === 0;
+ if ($firstLogin) {
+ \OC_Util::setupFS($userId);
+ // trigger creation of user home and /files folder
+ $userFolder = \OC::$server->getUserFolder($userId);
+ try {
+ // copy skeleton
+ \OC_Util::copySkeleton($userId, $userFolder);
+ } catch (NotPermittedException $ex) {
+ // read only uses
+ }
+
+ // trigger any other initialization
+ \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($user));
+ }
+ $user->updateLastLoginTimestamp();
+ return $firstLogin;
+ }
+}
diff --git a/src/components/SettingsForm.vue b/src/components/SettingsForm.vue
index 4e79259b..31712128 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/bootstrap.php b/tests/bootstrap.php
index 942fe846..511150ac 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -18,4 +18,9 @@
require_once __DIR__ . '/../../../tests/autoload.php';
require_once __DIR__ . '/../vendor/autoload.php';
+\OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests');
+\OC::$composerAutoloader->addPsr4('OCA\\UserOIDC\\BaseTest\\', dirname(__FILE__) . '/unit/MagentaCloud/', true);
+
Server::get(IAppManager::class)->loadApp('user_oidc');
+
+OC_Hook::clear();
diff --git a/tests/unit/BaseTest/OpenidTokenTestCase.php b/tests/unit/BaseTest/OpenidTokenTestCase.php
new file mode 100644
index 00000000..c0955093
--- /dev/null
+++ b/tests/unit/BaseTest/OpenidTokenTestCase.php
@@ -0,0 +1,141 @@
+realOidClaims;
+ }
+
+ public function getOidClientId() {
+ return 'USER_NC_OPENID_TEST';
+ }
+
+ public function getOidNonce() {
+ return 'CVMI8I3JZPALSL5DIM6I1PDP8SDSEN4K';
+ }
+
+ public function getOidClientSecret() {
+ return \Base64Url\Base64Url::encode('JQ17C99A-DAF8-4E27-FBW4-GV23B043C993');
+ }
+
+ public function getOidServerKey() {
+ return \Base64Url\Base64Url::encode('JQ17DAF8-C99A-4E27-FBW4-GV23B043C993');
+ }
+
+ public function getOidPrivateServerKey() {
+ 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() {
+ return \OCA\UserOIDC\Vendor\Firebase\JWT\JWK::parseKeySet([ 'keys' => [[
+ 'kty' => 'RSA',
+ 'e' => 'AQAB',
+ 'use' => 'sig',
+ 'kid' => '0123456789',
+ 'alg' => 'RS256',
+ 'n' => '6WCdDo8KuksEFaFlzvmsaoYhfOoMt5XgnX98dx-F1OUz53SG0lQlFt-xkwra5B4GZ-13lki0qCa2CjA1aLa9kEvDdYhz_0Uc5qOy5haDj8Jn547s6gFyaLzJ0RN5i5eKeDMHcjeEC0_NjiB2UNUFJJ61b2nXIlUvp_vBfKCv4A-8C3mLSbCKJQhX84QRDgt_Abz0MXj_ga72Ka2cwVLo4OFQAK5m57Qfu9ZvseMcgoinyhIQ18b98SkWinn3DM0W1KXLkWLk0S3XEMxLV1M7-9RLo4fgEGOpX1xmmM6KbsC5SxXvRUO7tjU-o35fcewDwXYHnRbxqhRkEFfWb7b8nQ'
+ ]]]);
+ }
+
+ public function getOidTestCode() {
+ return '66844608';
+ }
+
+ public function getOidTestState() {
+ return '4VSL5T274MJEMLZI1810HUFDA07CEPXZ';
+ }
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->app = new App(Application::APP_ID);
+ $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' => time(),
+ 'exp' => time() + 7200,
+ 'iat' => time(),
+ 'urn:custom.com:session_token' => 'ad0fff71-e013-11ec-9e17-39677d2c891c',
+ 'nonce' => 'CVMI8I3JZPALSL5DIM6I1PDP8SDSEN4K',
+ 'aud' => ['USER_NC_OPENID_TEST'] ];
+ }
+
+ protected function createSignToken(array $claims) : string {
+ // The algorithm manager with the HS256 algorithm.
+ $algorithmManager = new AlgorithmManager([
+ new RS256(),
+ ]);
+
+ // use a different key for an invalid signature
+ $jwk = new JWK($this->getOidPrivateServerKey());
+ $jwsBuilder = new JWSBuilder($algorithmManager);
+ $jws = $jwsBuilder->create() // We want to create a new JWS
+ ->withPayload(json_encode($claims)) // We set the payload
+ ->addSignature($jwk, ['alg' => 'RS256', 'kid' => '0123456789']) // We add a signature with a simple protected header
+ ->build();
+
+ $serializer = new CompactSerializer();
+ return $serializer->serialize($jws, 0);
+ }
+}
diff --git a/tests/unit/MagentaCloud/BearerSettingsTest.php b/tests/unit/MagentaCloud/BearerSettingsTest.php
new file mode 100644
index 00000000..eb142675
--- /dev/null
+++ b/tests/unit/MagentaCloud/BearerSettingsTest.php
@@ -0,0 +1,420 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
+use OCA\UserOIDC\AppInfo\Application;
+use OCA\UserOIDC\Command\UpsertProvider;
+
+use OCA\UserOIDC\Db\Provider;
+
+use OCA\UserOIDC\Db\ProviderMapper;
+use OCA\UserOIDC\Service\ProviderService;
+use OCP\IConfig;
+
+use OCP\IRequest;
+
+use OCP\Security\ICrypto;
+use PHPUnit\Framework\TestCase;
+
+
+use Symfony\Component\Console\Tester\CommandTester;
+
+class BearerSettingsTest extends TestCase {
+ /**
+ * @var ProviderService
+ */
+ private $provider;
+
+ /**
+ * @var IConfig;
+ */
+ private $config;
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $app = new \OCP\AppFramework\App(Application::APP_ID);
+ $this->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',
+ ]);
+ }
+}
diff --git a/tests/unit/MagentaCloud/BearerTokenServiceTest.php b/tests/unit/MagentaCloud/BearerTokenServiceTest.php
new file mode 100644
index 00000000..2c6ab49c
--- /dev/null
+++ b/tests/unit/MagentaCloud/BearerTokenServiceTest.php
@@ -0,0 +1,103 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
+
+use OCA\UserOIDC\MagentaBearer\SignatureException;
+use OCA\UserOIDC\MagentaBearer\TokenService;
+
+use PHPUnit\Framework\TestCase;
+
+class BearerTokenServiceTest extends TestCase {
+ public const EXPIRED_TOKEN = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdHMwMC5pZG0udmVyLnN1bC50LW9ubGluZS5kZSIsInVybjp0ZWxla29tLmNvbTppZG06YXQ6c3ViamVjdFR5cGUiOnsiZm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6bmFtZWlkLWZvcm1hdDphbmlkIiwicmVhbG0iOiJ2ZXIuc3VsLnQtb25saW5lLmRlIn0sImFjciI6InVybjp0ZWxla29tOm5hbWVzOmlkbTpUSE86MS4wOmFjOmNsYXNzZXM6cHdkIiwic3ViIjoiMTIwMDQ5MDEwMDAwMDAwMDA3MjEwMjA3IiwiaWF0IjoxNjM1NTgxODAyLCJuYmYiOjE2MzU1ODE4MDIsImV4cCI6MTYzNTU4OTAwMiwidXJuOnRlbGVrb20uY29tOmlkbTphdDphdXRoTlN0YXRlbWVudHMiOnsidXJuOnRlbGVrb206bmFtZXM6aWRtOlRITzoxLjA6YWM6Y2xhc3Nlczpwd2QiOnsiYXV0aGVudGljYXRpbmdBdXRob3JpdHkiOm51bGwsImF1dGhOSW5zdGFudCI6MTYzNTU4MTUzNX19LCJhdWQiOlsiaHR0cDovL2F1dGgubWFnZW50YWNsb3VkLmRlIl0sImp0aSI6IlNUUy0xZTIyYTA2Zi03OTBjLTQwZmItYWQxZC02ZGUyZGRjZjI0MzEiLCJ1cm46dGVsZWtvbS5jb206aWRtOmF0OmF0dHJpYnV0ZXMiOlt7Im5hbWUiOiJjbGllbnRfaWQiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIxMFRWTDBTQU0zMDAwMDAwNDkwMU5FWFRNQUdFTlRBQ0xPVUQwMDAwIn0seyJuYW1lIjoiZGlzcGxheW5hbWUiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiJubWNsb3VkMDFAdmVyLnN1bC50LW9ubGluZS5kZSJ9LHsibmFtZSI6ImVtYWlsIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoibm1jbG91ZDAxQHZlci5zdWwudC1vbmxpbmUuZGUifSx7Im5hbWUiOiJhbmlkIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMTIwMDQ5MDEwMDAwMDAwMDA3MjEwMjA3In0seyJuYW1lIjoiZDU1NiIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjAifSx7Im5hbWUiOiJkb210IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoidmVyLnN1bC50LW9ubGluZS5kZSJ9LHsibmFtZSI6ImYwNDgiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIxIn0seyJuYW1lIjoiZjA0OSIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJmMDUxIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMCJ9LHsibmFtZSI6ImY0NjAiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIwIn0seyJuYW1lIjoiZjQ2NyIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjAifSx7Im5hbWUiOiJmNDY4IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMCJ9LHsibmFtZSI6ImY0NjkiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIwIn0seyJuYW1lIjoiZjQ3MSIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjAifSx7Im5hbWUiOiJmNTU2IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMSJ9LHsibmFtZSI6ImY3MzQiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIwIn0seyJuYW1lIjoibWFpbkVtYWlsIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoibm1jbG91ZDAxQHZlci5zdWwudC1vbmxpbmUuZGUifSx7Im5hbWUiOiJzNTU2IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMCJ9LHsibmFtZSI6InVzdGEiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIxIn1dLCJ1cm46dGVsZWtvbS5jb206aWRtOmF0OnZlcnNpb24iOiIxLjAifQ.5zbr7Uvx2KmU8uR412jHhptWEjykJ_n2awBRcQL8fLE';
+ public const INVALID_SIGN_TOKEN = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdHMwMC5pZG0udmVyLnN1bC50LW9ubGluZS5kZSIsInVybjp0ZWxla29tLmNvbTppZG06YXQ6c3ViamVjdFR5cGUiOnsiZm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6bmFtZWlkLWZvcm1hdDphbmlkIiwicmVhbG0iOiJ2ZXIuc3VsLnQtb25saW5lLmRlIn0sImFjciI6InVybjp0ZWxla29tOm5hbWVzOmlkbTpUSE86MS4wOmFjOmNsYXNzZXM6cHdkIiwic3ViIjoiMTIwMDQ5MDEwMDAwMDAwMDA3MjEwMjA3IiwiaWF0IjoxNjM1NTgxODAyLCJuYmYiOjE2MzU1ODE4MDIsImV4cCI6MTYzNTU4OTAwMiwidXJuOnRlbGVrb20uY29tOmlkbTphdDphdXRoTlN0YXRlbWVudHMiOnsidXJuOnRlbGVrb206bmFtZXM6aWRtOlRITzoxLjA6YWM6Y2xhc3Nlczpwd2QiOnsiYXV0aGVudGljYXRpbmdBdXRob3JpdHkiOm51bGwsImF1dGhOSW5zdGFudCI6MTYzNTU4MTUzNX19LCJhdWQiOlsiaHR0cDovL2F1dGgubWFnZW50YWNsb3VkLmRlIl0sImp0aSI6IlNUUy0xZTIyYTA2Zi03OTBjLTQwZmItYWQxZC02ZGUyZGRjZjI0MzEiLCJ1cm46dGVsZWtvbS5jb206aWRtOmF0OmF0dHJpYnV0ZXMiOlt7Im5hbWUiOiJjbGllbnRfaWQiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIxMFRWTDBTQU0zMDAwMDAwNDkwMU5FWFRNQUdFTlRBQ0xPVUQwMDAwIn0seyJuYW1lIjoiZGlzcGxheW5hbWUiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiJubWNsb3VkMDFAdmVyLnN1bC50LW9ubGluZS5kZSJ9LHsibmFtZSI6ImVtYWlsIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoibm1jbG91ZDAxQHZlci5zdWwudC1vbmxpbmUuZGUifSx7Im5hbWUiOiJhbmlkIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMTIwMDQ5MDEwMDAwMDAwMDA3MjEwMjA3In0seyJuYW1lIjoiZDU1NiIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjAifSx7Im5hbWUiOiJkb210IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoidmVyLnN1bC50LW9ubGluZS5kZSJ9LHsibmFtZSI6ImYwNDgiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIxIn0seyJuYW1lIjoiZjA0OSIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJmMDUxIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMCJ9LHsibmFtZSI6ImY0NjAiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIwIn0seyJuYW1lIjoiZjQ2NyIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjAifSx7Im5hbWUiOiJmNDY4IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMCJ9LHsibmFtZSI6ImY0NjkiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIwIn0seyJuYW1lIjoiZjQ3MSIsIm5hbWVGb3JtYXQiOiJ1cm46Y29tOnRlbGVrb206aWRtOjEuMDphdHRybmFtZS1mb3JtYXQ6ZmllbGQiLCJ2YWx1ZSI6IjAifSx7Im5hbWUiOiJmNTU2IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMSJ9LHsibmFtZSI6ImY3MzQiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIwIn0seyJuYW1lIjoibWFpbkVtYWlsIiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoibm1jbG91ZDAxQHZlci5zdWwudC1vbmxpbmUuZGUifSx7Im5hbWUiOiJzNTU2IiwibmFtZUZvcm1hdCI6InVybjpjb206dGVsZWtvbTppZG06MS4wOmF0dHJuYW1lLWZvcm1hdDpmaWVsZCIsInZhbHVlIjoiMCJ9LHsibmFtZSI6InVzdGEiLCJuYW1lRm9ybWF0IjoidXJuOmNvbTp0ZWxla29tOmlkbToxLjA6YXR0cm5hbWUtZm9ybWF0OmZpZWxkIiwidmFsdWUiOiIxIn1dLCJ1cm46dGVsZWtvbS5jb206aWRtOmF0OnZlcnNpb24iOiIxLjAifQ.5zbr7Uvx2KmU8uR412jHhptWEjykJ_n2awBRcQL9fLE';
+ public const ENCRPYT1_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 const ENCRPYT2_SIGN_TOKEN = 'eyJwMnMiOiI2UWMyblpRYkxyRSIsInAyYyI6MTAwMCwiY3R5IjoiSldUIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImFsZyI6IlBCRVMyLUhTNTEyK0EyNTZLVyJ9.WqxhY5Qk2uYhlwqtH7JLb3l8QLxjo9Keq4p0iT_Xy4gIxbnQvIlkOYyf9b7QcBiOlStPm9-RMvt-MmgV_dibTvmrDtEq9CFy.J1NxAp-hkYrh4SHjvErdhw.cb9NQdiQDAoAJKn0jGm_nK9UzuxGcAdaycTwYv1IkYOPg4nteGXUoEZH0Yzh6KJh95vNBbVVhpKnQaYmYfnR7WzcufJJ9XpvsHGP2_KV_kItWTijClXun8Eyg5DDY5lK0Z656-f_walGoZnUxCGTxmra_3yOGbKMKaEq-lxBuf1lPdmi3IpGm1H4IOiiHLpu0ZjEUt_S-L4mOLmnc4TR17sEgB260e7-8np_GqwN5vns7ug3-M_AGFJUyDqKV6hRGu3qn7lLJynldNhIEitT759O-PWyCEff353WZv9d9PwdOQDE8N_pHRgm2JVDGeagDRzygEqhSMwct5o_IWJWNQs6zllnNYGyaB8B9fu9UhFq2dutIS24JwERnlNUcy_RXGq8CGEmnctEX_Has3fXUHanBqBGPJW7cZ2sei5O64YkndCMvePAWaScyd3j9QGvYHrBsriecxk565wjU905OHyXBax_UupbPeCKivDYOWou-HESV_xMHJhFqLJKDiCT7g-33OgXpLPpkzNi4a66KCYXngkYeCUhoGV_W7sVYHDe7GPwz99V7-6YuFnCI4qAvZkUQuzUtDILzrP8PBvsQ6DikYWkqcp9ypAMdej3OTh0TNZdu1ROEYFQB5eNv6I5N-V1ifD9WO1ch5ijYJ1xX-4FbUGt9d6Y-voPzR-6LK5NKhLs9xhNVv26mUZ3HNH78-qoZxXTrHtiarrkE7qc60-oh9BH0XZKsSZcCLpxukX8ibFl4-adYsFhFRAzU-XZT_LdXsBmX3U4DRGC-maM4Bqretaj5DdtjQrPuSETVNO8cMb8rf4UdLBKdXYpp2uqtbIhu327BDCtSptQ5uiZjwlxkMzYFBWCx0W6KN9PPvXhrKpPUzZ_rN98JdSZ0AtyQbR0Oqos7jF5OXs2HDEtOa9EQora3hVGiaOpy-u-AruQ-5SDl0Yh_1n3O2sI0HMO1fMvPEwq6GbetLg3a_xHNvPV6gTZv4-vhVG7b53m526pvx86pYqRp1XKCzQwBTsVUSDAW4mIqtu1R2Yilnb0Ep6wHic0n710YRco4H_tkRr7nYd526WxXYe_neEdD2du-3m3eakZ1IKOdcqiSNvPXyNTDpAPl6U9ap7VsqzksfO3Xo06RwbMv9HcLf9hqWYS6HYRvVVLcShTF9yVMc-XWIZaS0tL_M4jcD7SJsFhLo5p021j68vYoZFcniqFKRwC7ChoBv2Vh4L-eu9o9qlLkUm56g5QhZWQr7OuLxtoKpfoNuRcsg6cNl2TPDl1Hd36s6LtpHneEDaBuRY-s-46pa_gdYWN5i_LHUO807ZE6OmvFBLXy9qY38L5zoXm1FUtP6Td1kXnsiYiluSz8PFelqmkqYALRc1byfvjtjnZBAyMQ9P4xTP3_mEaPKw18O3NYtANYTJjvPvzxuDi7h_slKryJqtvkUcUUd1g45Qibj-jIW9WZdEEWjmDFQ9PA5CB32CfLIq0BqPCuwpFSHiukQ9ZKIpkw0aaDDS25MtwTc3zieiFxfvLSh3Y0lZrcStogk8f4FZecIM7Gi2joGBKZEHg5l30C4JlrZhnxVoIW197XVDDcXl_tRUFaxKAjyRCqQRMcFbapH1pTdA5Hqbwwron6GPnfZbSQoZmSNUuVbphK4DMMDlaMi9nNmIc5ckcR93m0io7fQkcMzAMjwcmMdWqy9boEva8ZEagmRlFoF0ryTsWNCe839dYtZAYuvDzh2JlG6lkisH5Begrx2sK6krKg0hKuvl2oy-eL0iPHff8AxGyrXp8VKF_afikaZyepIavfyW4AXg_1eSh_CP_WVxIvUpCUwdamoowYP0bvTNn_5dwywWD4lgF6FiQygBPCs7A3mQRUsvTyja0sS2LI7yRMWKiJ98mLt3aLNWUCwc4T37Uprk3yQjwFWZXBLsb40sCy_pOwuBzRsabANLbQ9KBThoeLp4FTrCXAK8VdVgUvZyzW8cuOix_EuV32V0HTYQpfbOVlOriAvT2_u2p7QQD8NjgTwy2XEI6zoIXOiXCZJ1ijowCKdmck95nOr4C50_bOFhQTInt3FAlxaAnKRWpdZ5IQ5xDqCsIY4T5gMAbm5SmHmDbwvGTsC7Ugsd6yattg907pmpFF_S9oR6hxKoxV9QfWdVcpXBgvYAc8k0zia1RyCUChAr76AMwHSpmuqGobhBU0J0Cpz11WA9QCQJMNcIezw07Dj-cf-XHbrVxGuXnJy7D1HrKddj8naPuTiRXw4UreJo4yBYQJxu2u1NXZfWyqgVdNi5hyH8F6l2fkMUD4Zmesplmp-NGsCxWV4KCioZXtZit_bpViBeqGQ1GTzvmYCgftqrZ_lJRCAbYjkZ_jZKfnysErxpp5fBpJYgBIUMLaKo4X47edQdygqZGC-58DFO5PTeCRSDcILYUgddTLG4D4lXg2l4SFGdidct-WSNHQhbTpjZeZZ4cX-fgZ2MzjcMf60rNRvp_AkvEqfdXEmnbWhL0820szKWaSPaJXqyapAaW4L4CYJGaIS8q9o8sZNQaMcAOzBVBdFS84AhnnIJeNQHxyUiVhR966KXewj7qKgxP9_bIkRLp3CoWvN8YsJXpg2NjryJNgTeswBdxTJiNeYM1v15rRMDBkpkLB-MfuGRjBnPiNB_KpTMfHdPi_J1d3wyOYT3bk27BIl43Mpubmz-2kyyyCZko9C7QvELaEgGNpKDgMfv6viqHw3BpLvBcMIsLxaT1arAHagSMyPu5KDKuAfD93Xl1ydEnkkSN11zmTks7rk7S3oc3kMPNyKrjhzXFMShgpjHBmJvSBimZgBUG9R4VmjSEjffvw55PZakAzA4f0TfN2hEgFdH1ABZoqGDrJXFRmjZY9hlGREwZ5DzV61144OShZ4yIagF2Efd7T4JBcu_9NLFg74M_CNCIJdJRerdOUhk2i7v2jcVT-N19Uo3CB6oQ9YfOecRqcl9jbX-r1GjnpYjdzEtYuuce03XNLVH5R__WgUssTYvupvCcPa8C9ZASSUQKVnM0r3-dD52E2JEBON123Z69OkUfx9QXqgJirT2mfwlpbarZ5Fk9DgE9J5M960DjUdRi31iP-KC8TDLJpoMyCvKLYKVdrqFwDx2xJRTOhpN02TWpkqMGXUech3QUndyEORq_FVgY3Xl1RXd9XFKK9Bv403lPIqt0421IFazWA6UAB5La0nf1iPrbkF9x-4uUBnLrEV20mlXWNh8X5FqVErjmqhQoAZwON1F-wRBju_bS8JZDpSYnzE6_8_550b-d9ZXtY1FD5b0uuF-6sU0gjiZmR9NuSc5JN-g_M8IrU8wCeuBovKobuvmUh1v2PXjJJ5iG1sjs3U3UnoaMdKMaUiMGf_yZLRTZFaMGpFfynJfksYUETaQ58hpjG4hIoNEBkMiC41O0SlsqyBAJ2R249K9BByULup504LdwVdRkyhoDRulZ7pEHxZbU9jKMvjPq9bfvfzHqPnSXzzFuQpO_HOsJZlk0Z1f8FtPAFCwCCclpRdvXOursYEnTIcpYLx4mZrr0wWUsmsxCYZHbEXEdtwTm9tJEsOMcLzNic2C9FZgQuE56Hyh5cLTOsEA3C8eoZBhVfxpVExJTSFal63_1wMvymXdXbLiDlGHTAnnPhL5RtaD6aFprNVEiK56vd7cyao7P_g2pciVkJ6EpvjdYCKOI_9uQGZTrdU2HEM__5l_b6ejIXh1EzcttCdqUOb3dUNEGfdMCMJdIYfYttlVmEGALlXTnvy4kH-XgaiGZiKxAU0ZKvCysqbd8GtXbSyHQ5AS8qHx3pxR8_Mws6rV55O5uycGYYCYWTfeZ0-8UBEKFAnSWi8MvIilsurAmYj8r9etUOLZ9ah-lKjedmURU3jQfYFa7r_-dcTJbRipzY5cNUKE6hJkdn0i9s3mz-fT-j0LcKEGyLfij55zUkKFQrkwyj3X6f1X_YcTkxXoSOZ23_zLisQIecOSgaPTyIiKQr7G_cwnoUBLNgUH_GB66uirM5Q2hhBD8DIToJYybaSD8XwScGtp3PirfVmP2ZbkFdh1021Mx65u3lh5Lpy9NVfyZ0m4GWGFRwPjqL7AcEtoPGi-PqEh61NrXNlKwsT_IMc3_D1hOb1CAYaNX3QjGYfgncc3tSpz--ZJvq7eDGNj6Z63qudum-pm7Sfgu9eBcknlw_TKjp67Ttnzt6UECZD47DFAgcAAnCs9nEthOU6B-J9hLYaLy2z3vGFyBPfaEH-B-YHRlriIaM5vCTHEmV3gY2iJxFA1DD7VDj3vx6Qw9uMLbcfl4eVXQRNs-HiW-ML6yUv0_zxIspC8HTX1l62vMi2_ofue9lDtjKfxVV-2inRhjae1fhViHb7DcwArnw2XaZYlTC0Oe_neA2pY4DuTAAuMikfQbaf50sU-gszAD_Xmmh6WvDHr4FdrTtf8ew3I1YCmP5lguc_w0QuC-fAAsVvz7bsIcSMFSFWGB71H9dAGSQ53bmboqUrRL-kUjLEF2hF9FqnVGmsx6lH9eMn9tWCApwm1zQNNMM5c.a1VctJwTCcJQ9LC1xGoyKn_2743jHhGpU5G8ucFR2ts';
+
+ /**
+ * @var ProviderService
+ */
+ private $provider;
+
+ /**
+ * @var tokenService
+ */
+ private $tokenService;
+
+ public function setUp(): void {
+ parent::setUp();
+ $this->tokenService = \OC::$server->get(TokenService::class);
+ $this->access_secret = \Base64Url\Base64Url::encode('JQ17C99A-DAF8-4E27-FBW4-GV23B043C993');
+ }
+
+ public function testDecodeAndValidSignature() {
+ $testtoken = self::EXPIRED_TOKEN;
+
+ $serializerManager = new \Jose\Component\Signature\Serializer\JWSSerializerManager([
+ new \Jose\Component\Signature\Serializer\CompactSerializer()
+ ]);
+
+ $decodedToken = $this->tokenService->decryptToken($testtoken, $this->access_secret);
+ $this->tokenService->verifySignature($decodedToken, $this->access_secret);
+ $claims = $this->tokenService->decode($decodedToken);
+ $this->assertNotNull($claims->exp);
+ $this->assertNotNull($claims->aud);
+ }
+
+ public function decryptDecodeAndValidate(string $testtoken) {
+ $serializerManager = new \Jose\Component\Signature\Serializer\JWSSerializerManager([
+ new \Jose\Component\Signature\Serializer\CompactSerializer()
+ ]);
+
+ $decodedToken = $this->tokenService->decryptToken($testtoken, $this->access_secret);
+ $this->tokenService->verifySignature($decodedToken, $this->access_secret);
+ $claims = $this->tokenService->decode($decodedToken);
+ $this->assertNotNull($claims->exp);
+ $this->assertNotNull($claims->aud);
+ return $claims;
+ }
+
+ public function testDecryptDecodeAndValidSignature1() {
+ //this is not a good unittest style: $this->expectNotToPerformAssertions();
+ $claims = $this->decryptDecodeAndValidate(self::ENCRPYT1_SIGN_TOKEN);
+ $this->assertEquals('10TESTSAM30000004901VOLKERKRIEGEL0000000', $claims->{'urn:telekom.com:client_id'});
+ }
+
+ public function testDecryptDecodeAndValidSignature2() {
+ //this is not a good unittest style: $this->expectNotToPerformAssertions();
+ $claims = $this->decryptDecodeAndValidate(self::ENCRPYT2_SIGN_TOKEN);
+ }
+
+ public function testDecodeAndInvalidSignature() {
+ $this->expectException(SignatureException::class);
+ $testtoken = self::INVALID_SIGN_TOKEN;
+
+ $serializerManager = new \Jose\Component\Signature\Serializer\JWSSerializerManager([
+ new \Jose\Component\Signature\Serializer\CompactSerializer()
+ ]);
+
+ $decodedToken = $this->tokenService->decryptToken($testtoken, $this->access_secret);
+ $this->tokenService->verifySignature($decodedToken, $this->access_secret);
+ }
+}
diff --git a/tests/unit/MagentaCloud/BearerTokenTestCase.php b/tests/unit/MagentaCloud/BearerTokenTestCase.php
new file mode 100644
index 00000000..16a6615f
--- /dev/null
+++ b/tests/unit/MagentaCloud/BearerTokenTestCase.php
@@ -0,0 +1,225 @@
+realExampleClaims;
+ }
+
+ /**
+ * Test bearer secret
+ */
+ public function getTestBearerSecret() {
+ return \Base64Url\Base64Url::encode('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);
+ $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' => time(),
+ 'nbf' => time(),
+ 'exp' => time() + 7200,
+ 'urn:telekom.com:idm:at:authNStatements' => [
+ 'urn:telekom:names:idm:THO:1.0:ac:classes:pwd' => [
+ 'authenticatingAuthority' => null,
+ 'authNInstant' => time() ]
+ ],
+ '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 {
+ // The algorithm manager with the HS256 algorithm.
+ $algorithmManager = new AlgorithmManager([
+ new HS256(),
+ ]);
+
+ if (!$invalidate) {
+ $jwk = new JWK([
+ 'kty' => 'oct',
+ 'k' => $signKey]);
+ } else {
+ // use a different key for an invalid signature
+ $jwk = new JWK([
+ 'kty' => 'oct',
+ 'k' => 'BnWHlEdffC0hfKSxrh01g7/M3djHIiOU6jNwJChYWP8=']);
+ }
+ // We instantiate our JWS Builder.
+ $jwsBuilder = new JWSBuilder($algorithmManager);
+
+ $jws = $jwsBuilder->create() // We want to create a new JWS
+ ->withPayload(json_encode($claims)) // We set the payload
+ ->addSignature($jwk, ['alg' => 'HS256']) // We add a signature with a simple protected header
+ ->build();
+
+ return $jws;
+ }
+
+ protected function setupSignedToken(array $claims, string $signKey) {
+ $serializer = new \Jose\Component\Signature\Serializer\CompactSerializer();
+ return $serializer->serialize($this->signToken($claims, $signKey), 0);
+ }
+
+ protected function setupEncryptedToken(JWS $token, string $decryptKey) {
+ // The key encryption algorithm manager with the A256KW algorithm.
+ $keyEncryptionAlgorithmManager = new AlgorithmManager([
+ new PBES2HS512A256KW(),
+ new RSAOAEP256(),
+ new ECDHESA256KW()
+ ]);
+ // The content encryption algorithm manager with the A256CBC-HS256 algorithm.
+ $contentEncryptionAlgorithmManager = new AlgorithmManager([
+ new A256CBCHS512(),
+ ]);
+ // The compression method manager with the DEF (Deflate) method.
+ $compressionMethodManager = new CompressionMethodManager([
+ new Deflate(),
+ ]);
+ $signSerializer = new \Jose\Component\Signature\Serializer\CompactSerializer();
+
+ $jwk = new JWK([
+ 'kty' => 'oct',
+ 'k' => $decryptKey]);
+
+ // We instantiate our JWE Builder.
+ $jweBuilder = new JWEBuilder(
+ $keyEncryptionAlgorithmManager,
+ $contentEncryptionAlgorithmManager,
+ $compressionMethodManager
+ );
+
+ $jwe = $jweBuilder
+ ->create() // We want to create a new JWE
+ ->withPayload($signSerializer->serialize($token, 0)) // We set the payload
+ ->withSharedProtectedHeader([
+ 'alg' => 'PBES2-HS512+A256KW', // Key Encryption Algorithm
+ 'enc' => 'A256CBC-HS512', // Content Encryption Algorithm
+ 'zip' => 'DEF' // We enable the compression (just for the example).
+ ])
+ ->addRecipient($jwk)
+ ->build(); // We build it
+
+ $encryptionSerializer = new \Jose\Component\Encryption\Serializer\CompactSerializer(); // The serializer
+ return $encryptionSerializer->serialize($jwe, 0);
+ }
+
+
+ protected function setupSignEncryptToken(array $claims, string $secret, bool $invalidate = false) {
+ 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 00000000..842b9466
--- /dev/null
+++ b/tests/unit/MagentaCloud/HeaderBearerTokenTest.php
@@ -0,0 +1,236 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
+use OCA\UserOIDC\AppInfo\Application;
+use OCA\UserOIDC\BaseTest\BearerTokenTestCase;
+
+use OCA\UserOIDC\Db\Provider;
+use OCA\UserOIDC\Db\ProviderMapper;
+use OCA\UserOIDC\Db\UserMapper;
+use OCA\UserOIDC\MagentaBearer\MBackend;
+use OCA\UserOIDC\MagentaBearer\TokenService;
+use OCA\UserOIDC\Service\DiscoveryService;
+use OCA\UserOIDC\Service\ProviderService;
+
+use OCA\UserOIDC\Service\ProvisioningEventService;
+use OCP\EventDispatcher\IEventDispatcher;
+
+use OCP\IConfig;
+
+//use OCA\UserOIDC\Db\User;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+
+use OCP\Security\ICrypto;
+use Psr\Log\LoggerInterface;
+
+class HeaderBearerTokenTest extends BearerTokenTestCase {
+
+ /**
+ * @var ProviderService
+ */
+ private $provider;
+
+ /**
+ * @var MBackend
+ */
+ private $backend;
+
+ /**
+ * @var IConfig;
+ */
+ private $config;
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $app = new \OCP\AppFramework\App(Application::APP_ID);
+ $this->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->getTestBearerSecret());
+
+ $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($this->any())
+ ->method('getSetting')
+ ->with($this->anything(), $this->logicalOr($this->equalTo(ProviderService::SETTING_CHECK_BEARER),
+ $this->equalTo(ProviderService::SETTING_MAPPING_UID)))
+ ->willReturnCallback(function ($id, $field, $default) :string {
+ if ($field === ProviderService::SETTING_MAPPING_UID) {
+ return 'sub';
+ } elseif ($field === ProviderService::SETTING_CHECK_BEARER) {
+ return '1';
+ } else {
+ return '';
+ }
+ });
+
+
+ $user = $this->createMock(IUser::class);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('1200490100000000100XXXXX');
+ $user->expects($this->any())
+ ->method('getDisplayName')
+ ->willReturn('nmc01');
+ $user->expects($this->any())
+ ->method('getEMailAddress')
+ ->willReturn('nmc01@ver.sul.t-online.de');
+
+ $userManager = $this->createMock(IUserManager::class);
+ $userManager->expects($this->any())
+ ->method('get')
+ ->willReturn($user);
+
+ $provisioningService = $this->createMock(ProvisioningEventService::class);
+ $provisioningService->expects($this->any())
+ ->method('provisionUser')
+ ->willReturn($user);
+
+ $this->backend = new MBackend($app->getContainer()->get(IConfig::class),
+ $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),
+ $app->getContainer()->get(DiscoveryService::class),
+ $this->providerMapper,
+ $this->providerService,
+ $userManager,
+ $crypto,
+ $app->getContainer()->get(TokenService::class),
+ $provisioningService);
+ }
+
+ public function testValidSignature() {
+ $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->b64BearerToken);
+ $this->requestMock->expects($this->any())
+ ->method('getHeader')
+ ->with($this->equalTo(Application::OIDC_API_REQ_HEADER))
+ ->willReturn('Bearer ' . $testtoken);
+
+ $this->assertTrue($this->backend->isSessionActive());
+ $this->assertEquals('1200490100000000100XXXXX', $this->backend->getCurrentUserId());
+ }
+
+ public function testInvalidSignature() {
+ $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->b64BearerToken);
+ $invalidSignToken = mb_substr($testtoken, 0, -1); // shorten sign to invalidate
+ $this->requestMock->expects($this->any())
+ ->method('getHeader')
+ ->with($this->equalTo(Application::OIDC_API_REQ_HEADER))
+ ->willReturn('Bearer ' . $invalidSignToken);
+
+ $this->assertTrue($this->backend->isSessionActive());
+ $this->assertEquals('', $this->backend->getCurrentUserId());
+ }
+
+ public function testEncryptedValidSignature() {
+ $testtoken = $this->setupSignEncryptToken($this->getRealExampleClaims(), $this->b64BearerToken);
+ $this->requestMock->expects($this->any())
+ ->method('getHeader')
+ ->with($this->equalTo(Application::OIDC_API_REQ_HEADER))
+ ->willReturn('Bearer ' . $testtoken);
+
+ $this->assertTrue($this->backend->isSessionActive());
+ $this->assertEquals('1200490100000000100XXXXX', $this->backend->getCurrentUserId());
+ }
+
+ public function testEncryptedInvalidSignature() {
+ $invalidEncToken = $this->setupSignEncryptToken($this->getRealExampleClaims(),
+ $this->b64BearerToken, true);
+ $this->requestMock->expects($this->any())
+ ->method('getHeader')
+ ->with($this->equalTo(Application::OIDC_API_REQ_HEADER))
+ ->willReturn('Bearer ' . $invalidEncToken);
+
+ $this->assertTrue($this->backend->isSessionActive());
+ $this->assertEquals('', $this->backend->getCurrentUserId());
+ }
+
+ public const ENCRPYT1_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() {
+ $this->requestMock->expects($this->any())
+ ->method('getHeader')
+ ->with($this->equalTo(Application::OIDC_API_REQ_HEADER))
+ ->willReturn('Bearer ' . self::ENCRPYT1_SIGN_TOKEN);
+
+ $this->assertTrue($this->backend->isSessionActive());
+ $this->assertEquals('', $this->backend->getCurrentUserId());
+ }
+
+ public const ENCRPYT2_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() {
+ $this->requestMock->expects($this->any())
+ ->method('getHeader')
+ ->with($this->equalTo(Application::OIDC_API_REQ_HEADER))
+ ->willReturn('Bearer ' . self::ENCRPYT2_SIGN_TOKEN);
+
+ $this->assertTrue($this->backend->isSessionActive());
+ $this->assertEquals('', $this->backend->getCurrentUserId());
+ }
+}
diff --git a/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php b/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php
new file mode 100644
index 00000000..85ac4d2a
--- /dev/null
+++ b/tests/unit/MagentaCloud/ProvisioningEventServiceTest.php
@@ -0,0 +1,481 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ */
+
+declare(strict_types=1);
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\Authentication\Token\IProvider;
+use OC\Security\Crypto;
+use OCA\UserOIDC\AppInfo\Application;
+use OCA\UserOIDC\BaseTest\OpenidTokenTestCase;
+use OCA\UserOIDC\Controller\LoginController;
+use OCA\UserOIDC\Db\Provider;
+use OCA\UserOIDC\Db\ProviderMapper;
+use OCA\UserOIDC\Db\SessionMapper;
+use OCA\UserOIDC\Db\UserMapper;
+use OCA\UserOIDC\Event\AttributeMappedEvent;
+use OCA\UserOIDC\Event\UserAccountChangeEvent;
+use OCA\UserOIDC\Service\DiscoveryService;
+use OCA\UserOIDC\Service\LdapService;
+use OCA\UserOIDC\Service\LocalIdService;
+use OCA\UserOIDC\Service\ProviderService;
+use OCA\UserOIDC\Service\ProvisioningEventService;
+use OCP\Accounts\IAccountManager;
+use OCP\AppFramework\App;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Http\Client\IClient;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IAvatarManager;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use OCP\IL10N; // deprecated!
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\IUserSession;
+
+
+use OCP\Security\ISecureRandom;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+
+class ProvisioningEventServiceTest extends OpenidTokenTestCase {
+ /**
+ * Set up needed system and app configurations
+ */
+ protected function getConfigSetup() :MockObject {
+ $config = $this->getMockForAbstractClass(IConfig::class);
+
+ $config->expects($this->any())
+ ->method('getSystemValue')
+ ->with($this->logicalOr($this->equalTo('user_oidc'), $this->equalTo('secret')))
+ ->willReturn($this->returnCallback(function ($key, $default) {
+ if ($key == 'user_oidc') {
+ return [
+ 'auto_provisioning' => true,
+ ];
+ } elseif ($key == 'secret') {
+ return 'Streng_geheim';
+ }
+ }));
+ return $config;
+ }
+
+ /**
+ * Prepare a proper session as if the handshake with an
+ * OpenID authenticator entity has already been done.
+ */
+ protected function getOidSessionSetup() :MockObject {
+ $session = $this->getMockForAbstractClass(ISession::class);
+
+ $session->expects($this->any())
+ ->method('get')
+ ->willReturn($this->returnCallback(function ($key) {
+ $values = [
+ 'oidc.state' => $this->getOidTestState(),
+ 'oidc.providerid' => $this->getProviderId(),
+ 'oidc.nonce' => $this->getOidNonce(),
+ 'oidc.redirect' => 'https://welcome.to.magenta'
+ ];
+
+ return $values[$key] ? $values[$key] : 'some_' . $key;
+ }));
+ $this->sessionMapper = $this->getMockBuilder(SessionMapper::class)
+ ->setConstructorArgs([ $this->getMockForAbstractClass(IDBConnection::class) ])
+ ->getMock();
+ $this->sessionMapper->expects($this->any())
+ ->method('createSession');
+
+ return $session;
+ }
+
+ /**
+ * Prepare a proper session as if the handshake with an
+ * OpenID authenticator entity has already been done.
+ */
+ protected function getProviderSetup() :MockObject {
+ $provider = $this->getMockBuilder(Provider::class)
+ ->addMethods(['getClientId', 'getClientSecret'])
+ ->getMock();
+ $provider->expects($this->any())
+ ->method('getClientId')
+ ->willReturn($this->getOidClientId());
+ $provider->expects($this->once())
+ ->method('getClientSecret')
+ ->willReturn($this->crypto->encrypt($this->getOidClientSecret()));
+ $this->providerMapper->expects($this->once())
+ ->method('getProvider')
+ ->with($this->equalTo($this->getProviderId()))
+ ->willReturn($provider);
+
+ return $provider;
+ }
+
+
+ /**
+ * Prepare a proper mapping configuration for the provider
+ */
+ protected function getProviderServiceSetup() :MockObject {
+ $providerService = $this->getMockBuilder(ProviderService::class)
+ ->setConstructorArgs([ $this->config, $this->providerMapper])
+ ->getMock();
+ $providerService->expects($this->any())
+ ->method('getSetting')
+ ->with($this->equalTo($this->getProviderId()), $this->logicalOr(
+ $this->equalTo(ProviderService::SETTING_MAPPING_UID),
+ $this->equalTo(ProviderService::SETTING_MAPPING_DISPLAYNAME),
+ $this->equalTo(ProviderService::SETTING_MAPPING_QUOTA),
+ $this->equalTo(ProviderService::SETTING_MAPPING_EMAIL),
+ $this->anything()))
+ ->will($this->returnCallback(function ($providerid, $key, $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'
+ ];
+ return $values[$key];
+ }));
+ return $providerService;
+ }
+
+ /**
+ * Prepare a proper session as if the handshake with an
+ * OpenID authenticator entity has already been done.
+ */
+ protected function getUserManagerSetup() :MockObject {
+ $userManager = $this->getMockForAbstractClass(IUserManager::class);
+ $this->user = $this->getMockForAbstractClass(IUser::class);
+ $this->user->expects($this->any())
+ ->method('canChangeAvatar')
+ ->willReturn(false);
+
+ return $userManager;
+ }
+
+
+ /**
+ * This is the standard execution sequence until provisoning
+ * is triggered in LoginController, set up with an artificial
+ * yet valid OpenID token.
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->app = new App(Application::APP_ID);
+ $this->config = $this->getConfigSetup();
+ $this->crypto = $this->getMockBuilder(Crypto::class)
+ ->setConstructorArgs([ $this->config ])
+ ->getMock();
+
+ $this->request = $this->getMockForAbstractClass(IRequest::class);
+ $this->request->expects($this->once())
+ ->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 ])
+ ->getMock();
+ $this->discoveryService = $this->getMockBuilder(DiscoveryService::class)
+ ->setConstructorArgs([ $this->app->getContainer()->get(LoggerInterface::class),
+ $this->getMockForAbstractClass(IClientService::class),
+ $this->providerService,
+ $this->app->getContainer()->get(ICacheFactory::class) ])
+ ->getMock();
+ $this->discoveryService->expects($this->once())
+ ->method('obtainDiscovery')
+ ->willReturn([ 'token_endpoint' => 'https://whatever.to.discover/token',
+ 'issuer' => 'https:\/\/accounts.login00.custom.de' ]);
+ $this->discoveryService->expects($this->once())
+ ->method('obtainJWK')
+ ->willReturn($this->getOidPublicServerKey());
+ $this->session = $this->getOidSessionSetup();
+ $this->client = $this->getMockForAbstractClass(IClient::class);
+ $this->response = $this->getMockForAbstractClass(IResponse::class);
+ //$this->usersession = $this->getMockForAbstractClass(IUserSession::class);
+ $this->usersession = $this->getMockBuilder(IUserSession::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([
+ 'setUser',
+ 'login',
+ 'logout',
+ 'getUser',
+ 'isLoggedIn',
+ 'getImpersonatingUserID',
+ 'setImpersonatingUserID',
+ 'setVolatileActiveUser' // Diese Methode hinzufügen, falls sie gebraucht wird.
+ ])
+ ->addMethods([
+ 'completeLogin',
+ 'createSessionToken',
+ 'createRememberMeToken'
+ ])
+ ->getMock();
+
+ $this->usermanager = $this->getUserManagerSetup();
+ $this->groupmanager = $this->getMockForAbstractClass(IGroupManager::class);
+ $this->dispatcher = $this->app->getContainer()->get(IEventDispatcher::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->app->getContainer()->get(IConfig::class));
+ // here is where the token magic comes in
+ $this->token = [ 'id_token' =>
+ $this->createSignToken($this->getRealOidClaims(),
+ $this->getOidServerKey())];
+ $this->tokenResponse = $this->getMockForAbstractClass(IResponse::class);
+ $this->tokenResponse->expects($this->once())
+ ->method('getBody')
+ ->willReturn(json_encode($this->token));
+
+ // mock token retrieval
+ $this->client = $this->getMockForAbstractClass(IClient::class);
+ $this->client->expects($this->once())
+ ->method('post')
+ ->with($this->equalTo('https://whatever.to.discover/token'), $this->arrayHasKey('body'))
+ ->willReturn($this->tokenResponse);
+ $this->clientService = $this->getMockForAbstractClass(IClientService::class);
+ $this->clientService->expects($this->once())
+ ->method('newClient')
+ ->willReturn($this->client);
+ $this->registrationContext =
+ $this->app->getContainer()->get(Coordinator::class)->getRegistrationContext();
+ $this->loginController = new LoginController($this->request,
+ $this->providerMapper,
+ $this->providerService,
+ $this->discoveryService,
+ $this->app->getContainer()->get(LdapService::class),
+ $this->app->getContainer()->get(ISecureRandom::class),
+ $this->session,
+ $this->clientService,
+ $this->app->getContainer()->get(IUrlGenerator::class),
+ $this->usersession,
+ $this->usermanager,
+ $this->app->getContainer()->get(ITimeFactory::class),
+ $this->dispatcher,
+ $this->config,
+ $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->attributeListener = null;
+ $this->accountListener = null;
+ }
+
+ /**
+ * Seems like the event dispatcher requires explicit unregistering
+ */
+ public function tearDown(): void {
+ parent::tearDown();
+ if ($this->accountListener != null) {
+ $this->dispatcher->removeListener(UserAccountChangeEvent::class, $this->accountListener);
+ }
+ if ($this->attributeListener != null) {
+ $this->dispatcher->removeListener(AttributeMappedEvent::class, $this->attributeListener);
+ }
+ }
+
+ protected function mockAssertLoginSuccess() {
+ $this->usermanager->expects($this->once())
+ ->method('get')
+ ->willReturn($this->user);
+ $this->session->expects($this->exactly(2))
+ ->method('set')
+ ->withConsecutive([$this->equalTo('oidc.id_token'), $this->anything()],
+ [$this->equalTo('last-password-confirm'), $this->anything()]);
+ $this->usersession->expects($this->once())
+ ->method('setUser')
+ ->with($this->equalTo($this->user));
+ $this->usersession->expects($this->once())
+ ->method('completeLogin')
+ ->with($this->anything(), $this->anything());
+ $this->usersession->expects($this->once())
+ ->method('createSessionToken');
+ $this->usersession->expects($this->once())
+ ->method('createRememberMeToken');
+ }
+
+ protected function assertLoginRedirect($result) {
+ $this->assertInstanceOf(RedirectResponse::class,
+ $result, 'LoginController->code() did not end with success redirect: Status: ' .
+ strval($result->getStatus() . ' ' . json_encode($result->getThrottleMetadata())));
+ }
+
+ protected function assertLogin403($result) {
+ $this->assertInstanceOf(TemplateResponse::class,
+ $result, 'LoginController->code() did not end with 403 Forbidden: Actual status: ' .
+ strval($result->getStatus() . ' ' . json_encode($result->getThrottleMetadata())));
+ }
+
+ /**
+ * Test with the default mapping, no mapping by attribute events
+ * provisioning with successful result.
+ */
+ public function testNoMap_AccessOk() {
+ $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->assertEquals('https://welcome.to.magenta', $result->getRedirectURL());
+ }
+
+ /**
+ * For multiple reasons, uid should com directly from a token
+ * field, usually sub. Thus, uid is not remapped by event, even
+ * if you try with a listener.
+ */
+ public function testUidNoMapEvent_AccessOk() {
+ $this->mockAssertLoginSuccess();
+ $this->attributeListener = function (Event $event): void {
+ if ($event instanceof AttributeMappedEvent &&
+ $event->getAttribute() == ProviderService::SETTING_MAPPING_UID) {
+ $this->fail('UID event mapping not supported');
+ }
+ };
+ $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(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.magenta', $result->getRedirectURL());
+ }
+
+
+
+ /**
+ * Test displayname set by event scheduling and negative result
+ */
+ public function testDisplaynameMapEvent_NOk_NoRedirect() {
+ $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() {
+ $this->attributeListener = function (Event $event): void {
+ if ($event instanceof AttributeMappedEvent &&
+ $event->getAttribute() == ProviderService::SETTING_MAPPING_EMAIL) {
+ //$defaultUID = $event->getValue();
+ $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() {
+ $this->mockAssertLoginSuccess();
+ $this->attributeListener = function (Event $event): void {
+ if ($event instanceof AttributeMappedEvent) {
+ if ($event->getAttribute() == ProviderService::SETTING_MAPPING_UID) {
+ $this->fail('UID event mapping not supported');
+ } elseif ($event->getAttribute() == ProviderService::SETTING_MAPPING_DISPLAYNAME) {
+ $event->setValue('Lisa, Mona');
+ } elseif ($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('https://welcome.to.magenta', $result->getRedirectURL());
+ }
+}
diff --git a/tests/unit/MagentaCloud/RegistrationsTest.php b/tests/unit/MagentaCloud/RegistrationsTest.php
new file mode 100644
index 00000000..eb714da9
--- /dev/null
+++ b/tests/unit/MagentaCloud/RegistrationsTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ */
+
+declare(strict_types=1);
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCA\UserOIDC\AppInfo\Application;
+use OCA\UserOIDC\Service\ProvisioningEventService;
+use OCA\UserOIDC\Service\ProvisioningService;
+
+use PHPUnit\Framework\TestCase;
+
+class RegistrationsTest extends TestCase {
+ public function setUp() :void {
+ parent::setUp();
+
+ $this->app = new Application();
+ $coordinator = \OC::$server->get(Coordinator::class);
+ $this->app->register($coordinator->getRegistrationContext()->for('user_oidc'));
+ }
+
+ public function testRegistration() :void {
+ $provisioningService = $this->app->getContainer()->get(ProvisioningService::class);
+ $this->assertInstanceOf(ProvisioningEventService::class, $provisioningService);
+ }
+}
diff --git a/tests/unit/MagentaCloud/SamBearerTokenTest.php b/tests/unit/MagentaCloud/SamBearerTokenTest.php
new file mode 100644
index 00000000..0c87a33f
--- /dev/null
+++ b/tests/unit/MagentaCloud/SamBearerTokenTest.php
@@ -0,0 +1,85 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
+use OCA\UserOIDC\BaseTest\BearerTokenTestCase;
+
+
+use OCA\UserOIDC\MagentaBearer\InvalidTokenException;
+use OCA\UserOIDC\MagentaBearer\SignatureException;
+
+class SamBearerTokenTest extends BearerTokenTestCase {
+
+ /**
+ * @var ProviderService
+ */
+ private $provider;
+
+
+ public function setUp(): void {
+ parent::setUp();
+ }
+
+ public function testValidSignature() {
+ $this->expectNotToPerformAssertions();
+ $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->getTestBearerSecret());
+ //fwrite(STDERR, '[' . $testtoken . ']');
+ $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() {
+ $this->expectException(SignatureException::class);
+ $testtoken = $this->setupSignedToken($this->getRealExampleClaims(), $this->getTestBearerSecret());
+ $invalidSignToken = mb_substr($testtoken, 0, -1); // shorten sign to invalidate
+ // fwrite(STDERR, '[' . $testtoken . ']');
+ $bearerToken = $this->tokenService->decryptToken($invalidSignToken, $this->getTestBearerSecret());
+ $this->tokenService->verifySignature($bearerToken, $this->getTestBearerSecret());
+ $claims = $this->tokenService->decode($bearerToken);
+ $this->tokenService->verifyClaims($claims, ['http://auth.magentacloud.de']);
+ }
+
+ public function testEncryptedValidSignature() {
+ $this->expectNotToPerformAssertions();
+ $testtoken = $this->setupSignEncryptToken($this->getRealExampleClaims(), $this->getTestBearerSecret());
+ //fwrite(STDERR, '[' . $testtoken . ']');
+ $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() {
+ $this->expectException(InvalidTokenException::class);
+ $testtoken = $this->setupSignEncryptToken($this->getRealExampleClaims(), $this->getTestBearerSecret());
+ $invalidEncryption = mb_substr($testtoken, 0, -1); // shorten sign to invalidate
+ //fwrite(STDERR, '[' . $testtoken . ']');
+ $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']);
+ }
+}