diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1121d5e0..37afbfa0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["7.4"] + php-versions: ["7.4", "8.0", "8.1"] steps: - name: Setup PHP, with composer and extensions @@ -35,14 +35,14 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -54,9 +54,6 @@ jobs: - name: Install Composer dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - - name: Syntax check PHP - run: bash vendor/bin/check-syntax-php.sh - - name: Decide whether to run code coverage or not if: ${{ matrix.php-versions != '7.4' }} run: | @@ -90,7 +87,7 @@ jobs: - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get composer cache directory id: composer-cache @@ -131,14 +128,14 @@ jobs: - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -147,12 +144,6 @@ jobs: - name: Install Composer dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - - name: Syntax check YAML / XML / JSON - run: | - bash vendor/bin/check-syntax-yaml.sh - bash vendor/bin/check-syntax-xml.sh - bash vendor/bin/check-syntax-json.sh - quality: name: Quality control runs-on: [ubuntu-latest] @@ -169,14 +160,14 @@ jobs: - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -185,13 +176,13 @@ jobs: - name: Install Composer dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: build-data path: ${{ github.workspace }}/build - name: Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 - name: PHP Code Sniffer if: always() @@ -201,21 +192,19 @@ jobs: if: always() run: php vendor/bin/psalm --show-info=true - - name: Psalter - if: always() - run: php vendor/bin/psalter --issues=UnnecessaryVarAnnotation --dry-run - - build-conformance-suite: + conformance-suite: runs-on: ubuntu-latest env: - VERSION: release-v4.1.11 + SUITE_BASE_URL: https://localhost.emobix.co.uk:8443 + VERSION: release-v4.1.45 steps: - - name: Load Cached Conformance Suite Build - uses: actions/cache@v2 - id: cache + - uses: actions/checkout@v3 with: - path: ./conformance-suite - key: suite-${{ hashFiles('**/test.yml') }} + path: main + - name: Setup Python Dependencies + run: | + pip install --upgrade pip + pip install httpx - name: Conformance Suite Checkout if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: git clone --depth 1 --single-branch --branch $VERSION https://gitlab.com/openid/conformance-suite.git @@ -228,23 +217,6 @@ jobs: sed -i -e 's/localhost/localhost.emobix.co.uk/g' src/main/resources/application.properties sed -i -e 's/-B clean/-B -DskipTests=true/g' builder-compose.yml docker-compose -f builder-compose.yml run builder - - conformance-suite: - runs-on: ubuntu-latest - needs: - - build-conformance-suite - env: - SUITE_BASE_URL: https://localhost.emobix.co.uk:8443 - steps: - - uses: actions/checkout@v2 - with: - path: main - - name: Load Cached Conformance Suite Build - uses: actions/cache@v2 - id: cache - with: - path: ./conformance-suite - key: suite-${{ hashFiles('**/test.yml') }} - name: Run Conformance Suite working-directory: ./conformance-suite run: | diff --git a/CONFORMANCE_TEST.md b/CONFORMANCE_TEST.md index 796aca71..3e91c8dc 100644 --- a/CONFORMANCE_TEST.md +++ b/CONFORMANCE_TEST.md @@ -15,7 +15,7 @@ Clone the conformance test git repo, build the software and run it. git clone https://gitlab.com/openid/conformance-suite.git cd conformance-suite # Version 4.1.10 has a bug when building -git checkout release-v4.1.9 +git checkout release-v4.1.45 MAVEN_CACHE=./m2 docker-compose -f builder-compose.yml run builder docker-compose up ``` diff --git a/README.md b/README.md index f3addb7a..95ab8697 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ # simplesamlphp-module-oidc -> A SimpleSAMLphp module adding support for the OpenID Connect protocol. +> A SimpleSAMLphp module for OIDC OP support. -This module adds support for the OpenID Connect protocol through a SimpleSAMLphp module installable through Composer. +This module adds support for OpenID Provider role from the OpenID Connect protocol +through a SimpleSAMLphp module installable through Composer. It is based on +[Oauth2 Server from the PHP League](https://oauth2.thephpleague.com/) + +Currently supported flows are: +* Authorization Code flow, with PKCE support (response_type 'code') +* Implicit flow (response_type 'id_token token' or 'id_token') +* Plain OAuth2 Implicit flow (response_type 'token') +* Refresh Token flow [![Build Status](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml/badge.svg)](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml) [![Coverage Status](https://codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc/branch/master/graph/badge.svg)](https://app.codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc) @@ -14,40 +22,80 @@ This module adds support for the OpenID Connect protocol through a SimpleSAMLphp Installation can be as easy as executing: composer require simplesamlphp/simplesamlphp-module-oidc - -## Configuration -Once you install and configure the module checkout the [FAQ](FAQ.md) +### Configure the module -## Upgrading? +Copy the module config template file to the SimpleSAMLphp config directory: + + cp modules/oidc/config-templates/module_oidc.php config/ -If you are upgrading versions checkout the [upgrade guide](UPGRADE.md) +The options are self-explanatory, so make sure to go through the file and edit them as appropriate. ### Configure the database -Edit your `config/config.php` and check you configured at least the next parameters from the _database_ section: +This module uses a default SimpleSAMLphp database feature to store access/refresh tokens, user data, etc. +In order for this to work, edit your `config/config.php` and check 'database' related configuration. Make sure +you have at least the following parameters set: - 'database.dsn' => 'mysql:host=server;dbname=simplesamlphp', + 'database.dsn' => 'mysql:host=server;dbname=simplesamlphp;charset=utf8', 'database.username' => 'user', 'database.password' => 'password', -### Configure the template +### Run database migrations -This module used the new twig template system, so you need to configure the next option in `config/config.php`: +The module comes with some default SQL migrations which set up needed tables in the configured database. To run them, +open the _Federation_ tab from your _SimpleSAMLphp_ installation and select the option _OpenID Connect Installation_ +inside the _Tools_ section. Once there, all you need to do is press the _Install_ button and the schema will be created. - 'language.i18n.backend' => 'gettext/gettext', +### Relying Party (RP) Administration -### Configure the module +The module lets you manage (create, read, update and delete) approved RPs from the module user interface itself. -Copy the template file to the config directory: +Once the database schema has been created, you can open the _Federation_ tab from your _SimpleSAMLphp_ installation +and select the option _OpenID Connect Client Registry_ inside the _Tools_ section. - cp modules/oidc/config-templates/module_oidc.php config/ +Note that clients can be marked as confidential or public. If the client is not marked as confidential (it is public), +and is using Authorization Code flow, it will have to provide PKCE parameters during the flow. + +Client ID and secret will be generated, and can be seen after the client creation by clicking on the 'show' button. + +### Create RSA key pair + +During the authentication flow, generated ID Token and Access Token will be in a form of signed JSON Web token (JWS). +Because of the signing part, you need to create a public/private RSA key pair. + +To generate the private key, you can run this command in the terminal: + + openssl genrsa -out cert/oidc_module.key 2048 + +If you want to provide a passphrase for your private key, run this command instead: + + openssl genrsa -passout pass:myPassPhrase -out cert/oidc_module.key 2048 + +Now you need to extract the public key from the private key: + + openssl rsa -in cert/oidc_module.key -pubout -out cert/oidc_module.crt + +or use your passphrase if provided on private key generation: + + openssl rsa -in cert/oidc_module.key -passin pass:myPassPhrase -pubout -out cert/oidc_module.crt -and edit it. The options are self explained. +If you use a passphrase, make sure to also configure it in the `module_oidc.php` config file. -#### Private scopes +### Cron hook -This module support the basic OIDC scopes: openid, email, address, phone and profile. You can add your own private scopes in the `module_oidc.php` config file: +In order to purge expired tokens, this module requires [cron module](https://simplesamlphp.org/docs/stable/cron:cron) +to be enabled and configured. + +## Upgrading? + +If you are upgrading from a previous version, checkout the [upgrade guide](UPGRADE.md). + +## Additional considerations +### Private scopes + +This module support the basic OIDC scopes: openid, email, address, phone and profile. +However, you can add your own private scopes in the `module_oidc.php` config file, for example: ```php [ @@ -222,11 +209,26 @@ A permission can be disable by commenting it out. Users can visit the `https://example.com/simplesaml/module.php/oidc/clients/` to create and view their clients. -### Create client options +## OIDC Discovery -* Enabled: You can enable or disable a client. Disabled by default. -* Secure client: The client is secure if it is capable of securely storing a secret. Unsecure clients -must provide a PCKS token (code_challenge parameter during authorization phase). Disabled by default. +The module offers an OpenID Connect Discovery endpoint at URL: + + https://yourserver/simplesaml/module.php/oidc/openid-configuration.php + +### .well-known URL + +You can configure you web server (Apache, Nginx) in a way to serve the mentioned autodiscovery URL in a '.well-known' +form. Here are some sample configurations: + +#### nginx + location = /.well-known/openid-configuration { + rewrite ^(.*)$ /simplesaml/module.php/oidc/openid-configuration.php break; + proxy_pass https://localhost; + } + +#### Apache + + Alias /.well-known/openid-configuration "/path/to/simplesamlphp/module.php/oidc/openid-configuration.php" ## Using Docker @@ -247,7 +249,7 @@ docker run --name ssp-oidc-dev \ --mount type=bind,source="$(pwd)/docker/ssp/authsources.php",target=/var/simplesamlphp/config/authsources.php,readonly \ --mount type=bind,source="$(pwd)/docker/ssp/config-override.php",target=/var/simplesamlphp/config/config-override.php,readonly \ --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.crt",target=/var/simplesamlphp/cert/oidc_module.crt,readonly \ - --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.pem",target=/var/simplesamlphp/cert/oidc_module.pem,readonly \ + --mount type=bind,source="$(pwd)/docker/ssp/oidc_module.key",target=/var/simplesamlphp/cert/oidc_module.key,readonly \ --mount type=bind,source="$(pwd)/docker/apache-override.cf",target=/etc/apache2/sites-enabled/ssp-override.cf,readonly \ -p 443:443 cirrusid/simplesamlphp:1.19.0 ``` @@ -311,11 +313,10 @@ Visit the [OP](https://op.local.stack-dev.cirrusidentity.com/simplesaml/) and co Conformance tests are easier to run locally, see [CONFORMANCE_TEST.md](CONFORMANCE_TEST.md) -Work in Progress: - * Adding RPs to docker compose. Issue: the RP container refuses to connect to the OP's .well-known endpoint because - it uses self-signed certificates. This makes local testing difficult. - * Allow testing with different databases - ## Running Conformance Tests See [CONFORMANCE_TEST.md](CONFORMANCE_TEST.md) + +## Have more questions? + +Check the [FAQ](FAQ.md). \ No newline at end of file diff --git a/UPGRADE.md b/UPGRADE.md index afff2fc2..6ae4f253 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,10 @@ +# Version 2 to 3 + - Module code was refactored to make it compatible with SimpleSAMLphp v2 + - Default key name was changed from oidc_module.pem to oidc_module.key. If you don't set custom +key name using option 'privatekey' in module config file, make sure to change the file name of the +key from oidc_module.pem to oidc_module.key. + - Removed config option 'alwaysIssueRefreshToken' + - Removed config option 'alwaysAddClaimsToIdToken' # Version 1 to 2 diff --git a/composer.json b/composer.json index 94726d39..73282a00 100644 --- a/composer.json +++ b/composer.json @@ -27,23 +27,25 @@ "laminas/laminas-httphandlerrunner": "^1.1.0", "lcobucci/jwt": "^4.1", "league/oauth2-server": "^8.1.0", - "nette/forms": "^2.4", + "nette/forms": "^3", "psr/container": "^1.0", "psr/log": "^1.1", - "simplesamlphp/composer-module-installer": "^1.0", + "simplesamlphp/composer-module-installer": "^1.2", "spomky-labs/base64url": "^2.0", "steverhoades/oauth2-openid-connect-server": "^2.0", "web-token/jwt-framework": "^2.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.10", "friends-of-phpspec/phpspec-code-coverage": "^6.1", + "friendsofphp/php-cs-fixer": "^3", "phpspec/phpspec": "^7.1.0", "phpunit/php-code-coverage": "^9.0.0", "phpunit/phpcov": "^8.2.0", "phpunit/phpunit": "^9.0.0", - "simplesamlphp/simplesamlphp": "^1.19,<2", - "simplesamlphp/simplesamlphp-test-framework": "^0.1.9" + "simplesamlphp/simplesamlphp": "^v2.0.0", + "simplesamlphp/simplesamlphp-test-framework": "^1.2.1", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "^5.8" }, "config": { "preferred-install": { @@ -56,7 +58,7 @@ }, "autoload": { "psr-4": { - "SimpleSAML\\Module\\oidc\\": "lib/" + "SimpleSAML\\Module\\oidc\\": "src/" } }, "autoload-dev": { @@ -71,12 +73,7 @@ }, "scripts": { "pre-commit": [ - "vendor/bin/check-syntax-php.sh", - "vendor/bin/check-syntax-json.sh", - "vendor/bin/check-syntax-xml.sh", - "vendor/bin/check-syntax-yaml.sh", "vendor/bin/psalm", - "vendor/bin/psalter --issues=UnnecessaryVarAnnotation --dry-run", "vendor/bin/phpcs -p" ], "tests": [ diff --git a/config-templates/module_oidc.php b/config-templates/module_oidc.php index d70881d5..2452edbe 100644 --- a/config-templates/module_oidc.php +++ b/config-templates/module_oidc.php @@ -18,8 +18,8 @@ // The private key passphrase (optional) // 'pass_phrase' => 'secret', - // The cert and key for signing the ID token. Default names are oidc_module.pem and oidc_module.crt - // 'privatekey' => 'oidc_module.pem', + // The cert and key for signing the ID token. Default names are oidc_module.key and oidc_module.crt + // 'privatekey' => 'oidc_module.key', // 'certificate' => 'oidc_module.crt', // Tokens TTL @@ -64,18 +64,6 @@ 'client' => ['urn:example:oidc:manage:client'], ], - // The claims from the standard scopes should only be put in the ID token when no access token is issued - // For module backwards compatibility you can always include claims in the id token. - // @see https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.4 - // @deprecated option will be removed in v3. - 'alwaysAddClaimsToIdToken' => true, - - // Refresh token should only be released if the client requests it using the 'offline_access' scope. - // For module backwards compatibility you can always issue refresh token. - // @see https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess - // @deprecated option will be removed in v3. - 'alwaysIssueRefreshToken' => true, - // Settings regarding Authentication Processing Filters. // Note: OIDC authN state array will not contain all of the keys which are available during SAML authN, // like Service Provider metadata, etc. diff --git a/docker/Dockerfile b/docker/Dockerfile index e406b541..d33aa180 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM cirrusid/simplesamlphp:1.19.1 +FROM cirrusid/simplesamlphp:v2.0.0 RUN apt-get update && apt-get install -y sqlite3 # Prepopulate the DB with items needed for testing @@ -20,8 +20,8 @@ ADD docker/ssp/module_oidc.php /var/simplesamlphp/config/module_oidc.php ADD docker/ssp/authsources.php /var/simplesamlphp/config/authsources.php ADD docker/ssp/config-override.php /var/simplesamlphp/config/config-override.php ADD docker/ssp/oidc_module.crt /var/simplesamlphp/cert/oidc_module.crt -ADD docker/ssp/oidc_module.pem /var/simplesamlphp/cert/oidc_module.pem +ADD docker/ssp/oidc_module.key /var/simplesamlphp/cert/oidc_module.key ADD docker/apache-override.cf /etc/apache2/sites-enabled/ssp-override.cf RUN chown www-data /var/simplesamlphp/cert/oidc* \ - && chmod 660 /var/simplesamlphp/cert/oidc* \ No newline at end of file + && chmod 660 /var/simplesamlphp/cert/oidc* diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 235c8458..21f1ce1f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,6 +1,6 @@ services: http-proxy: - image: jwilder/nginx-proxy:0.8.0 + image: nginxproxy/nginx-proxy:1.2.1 ports: - "443:443" - "80:80" @@ -30,7 +30,6 @@ services: - ./docker/ssp/authsources.php:/var/simplesamlphp/config/authsources.php:ro - ./docker/ssp/config-override.php:/var/simplesamlphp/config/config-override.php:ro - ./docker/apache-override.cf:/etc/apache2/sites-enabled/ssp-override.cf:ro -# image: cirrusid/simplesamlphp:1.19.0 # oidc-rp still need work # oidc-rp: # ports: diff --git a/docker/nginx-certs/README.md b/docker/nginx-certs/README.md index 024e4b36..17e67af3 100644 --- a/docker/nginx-certs/README.md +++ b/docker/nginx-certs/README.md @@ -4,7 +4,7 @@ can sync them here. ```bash docker pull cirrusid/simplesamlphp:latest -docker run -v $PWD:/opt/tmp/certs cirrusid/simplesamlphp /bin/bash -c 'cp /etc/ssl/certs/${APACHE_CERT_NAME}.pem /opt/tmp/certs/default.crt && cp /etc/ssl/private/${APACHE_CERT_NAME}.key /opt/tmp/certs/default.key && openssl x509 -noout -enddate -in /opt/tmp/certs/default.crt > /opt/tmp/certs/expiration' +docker run -v $PWD:/opt/tmp/certs cirrusid/simplesamlphp /bin/bash -c 'cp /etc/ssl/certs/${APACHE_CERT_NAME}.key /opt/tmp/certs/default.crt && cp /etc/ssl/private/${APACHE_CERT_NAME}.key /opt/tmp/certs/default.key && openssl x509 -noout -enddate -in /opt/tmp/certs/default.crt > /opt/tmp/certs/expiration' ``` The file `expiration` will get updated with the current expiration date of the certificates. diff --git a/docker/nginx-certs/default.crt b/docker/nginx-certs/default.crt index eebfe059..79aeb4fc 100644 --- a/docker/nginx-certs/default.crt +++ b/docker/nginx-certs/default.crt @@ -1,33 +1,33 @@ -----BEGIN CERTIFICATE----- -MIIFUDCCBDigAwIBAgISBPBNYEW0eHtm/dWgeWftLVKOMA0GCSqGSIb3DQEBCwUA +MIIFTzCCBDegAwIBAgISA/Jlept1Qhhdiz41X6VdaHJLMA0GCSqGSIb3DQEBCwUA MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD -EwJSMzAeFw0yMTA2MDcxNzEyMzlaFw0yMTA5MDUxNzEyMzlaMC8xLTArBgNVBAMM +EwJSMzAeFw0yMTExMzAyMjU4MjNaFw0yMjAyMjgyMjU4MjJaMC8xLTArBgNVBAMM JCoubG9jYWwuc3RhY2stZGV2LmNpcnJ1c2lkZW50aXR5LmNvbTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBALIop3HcbChm4YFUWRlMlOiY+IEpFEgd2u9J -q1EAc6IEfT+3W1Y7Rr/grrnrV24g3ldIVyGyYCBrXMHox00kOI/KDR0TnTYMVYR8 -Xz5BUcJSE3bsIpTlMmnJ38X03NBOq45eQ+cKIU1m9NiVWgPxBF1yPdxgNaqnatg2 -iB36aFEv+o/C4FAKYKRk+ZJf1BLaIeL3nVAMSOm2CNnwWKfujEuZXm/um+aZXHlZ -Ft15R9Z2WWeU9zmP876CCXVdBE8vtp4CZZQCD3onSx/Cc7+1isJDFhE9hM5hNC5H -L7fiCQ4wlzPPUTd7/eTN6OIzS5EgvCnEUJQUyVRqmyB/OzcrbMcCAwEAAaOCAmEw -ggJdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH -AwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUG0vi9AOYg5jENoXCCsSvyLeY9aYw +hvcNAQEBBQADggEPADCCAQoCggEBANUGI0JUZ0AHg3xRivLRsRh0t+YcHw+N+dND +5e1xGyON8rxiUNxZjIJhUX6UrspSPDQj437xDdvH1M8XbdhpR5sx4/K4T20PtTSM +DpuFnpVdM3zfhy8m9o0ikhx44tlg1T7+LvjDF9yY4fFUy83iHesd/P8L/cefr5kz +gXnZthyWF+soUcFJ3NuTI8nI9ppWTahAxQ5cq20HQ6hu+thUiPE39bz6zDPUjkhM +g/xWAemNQj3tEpnlBi6ewbS4vWy1dJ+HCE4kfz3FjBx+jXURWQ2OLqCIH/2iT8wQ +oaqB1zKjX2Avp03t66ElnMxo4x+Fb9wJB5vVwVxMa4zXbVNQGocCAwEAAaOCAmAw +ggJcMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH +AwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUv/yi/9NC/ZtcfQqtkb4A+MW77p4w HwYDVR0jBBgwFoAUFC6zF7dYVsuuUAlA5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBH MCEGCCsGAQUFBzABhhVodHRwOi8vcjMuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKG Fmh0dHA6Ly9yMy5pLmxlbmNyLm9yZy8wLwYDVR0RBCgwJoIkKi5sb2NhbC5zdGFj ay1kZXYuY2lycnVzaWRlbnRpdHkuY29tMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5 -cHQub3JnMIIBBgYKKwYBBAHWeQIEAgSB9wSB9ADyAHcARJRlLrDuzq/EQAfYqP4o -wNrmgr7YyzG1P9MzlrW2gagAAAF55604FgAABAMASDBGAiEAipMJjfyK6sbjtP0m -GORkd2R81JgBbS1wVG5IMC5zu1kCIQDvGaiGjFqwRTZBQvI3uikxzh8KI/g28EUh -cGMXg+d7hQB3APZclC/RdzAiFFQYCDCUVo7jTRMZM7/fDC8gC8xO8WTjAAABeeet -N/YAAAQDAEgwRgIhAIU6yZ77o4g/c+d10DaVEeDjtgFHuN/Im2ghKFE4DW6wAiEA -u0d8wUNevgV+VaQ6MbYNoUBDQw9V6+chiOckxO8XKvAwDQYJKoZIhvcNAQELBQAD -ggEBAJSKmeSmUEoLDUhsySVr6CBDVpuRM5RrW+ZgKaAFZiA11nIZ8XoElkW4kNIo -ysWeTqskol8xj4Clm3MNkc5pMXKauK7ts/KH6QHnTi0cKoifFFm+Bb4hyPwM2b8D -BRsc4fAS+bM5/e4yNsFKI/sSxr1dkylv909mVALJkM3EitOMlVHcq7wpruH0ZL2U -mkuljeLrZwSUO05jH7r1voibaXeqWWIdsfLW/oQNnFeN4IWdajS0Ff0VnY6Tspun -irptgZ7rBWi9VW3keyV/R2lnIq4ZyZbZK2Wk7vdmaWq+hbPJ6oizgizu/lTn47qP -LuSGwcGg2dPqOwfxG5aykWSn74Y= +cHQub3JnMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHYARqVV63X6kSAwtaKJafTz +fREsQXS+/Um4havy/HD+bUcAAAF9c0j/QgAABAMARzBFAiBZ4oleDpke0bMLZhs8 +SVWrLCo71PRFOjhnJDUO+BCo2QIhAIF5OhvMPTlooFlsPGdgMOHGQuxne/URjF7Z +7HqhggNyAHcA36Veq2iCTx9sre64X04+WurNohKkal6OOxLAIERcKnMAAAF9c0kB +DwAABAMASDBGAiEAnBJQMiPsS9nbUWGzSU+Z+4Aho9aUZiyleFD17XZE5tgCIQCB +vBG2YcQNDkTiDHCGs4IcPGj8pYkr/Cn5WXIh1iJs8TANBgkqhkiG9w0BAQsFAAOC +AQEAslzvucE9ykWgCLkaq3mGaSLT/UD/2K2w/KoXXth0d0c5iRFHJljVsMVhkAFK +dJUz8TLpY+5Z9NFVm+ycDlWmn9VIeSwkrh4LtTHVTr1jg9JX97kQcvFjrwTdmPbm +G8tej+AZ7Qp8dL7ZbxvHz076zbA/TmzKVg++rToV6QObxSrXqWzmV5REKvrsM8KQ +tTw0Olk3FikFS0qu5y8zXh8Nd7e0G8vDD4e/4oWbhjoNzr6gJSgrjhiyBNZ3gvzH +hq/iUZ1gpBa3Wk5oTpceEZV5lMlI2hkV/AVXfSbZ3xBqHMpP36J63nv7vUBhhSkk +/OOUGcEGCNqBo5MaluzKJQFVCA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw diff --git a/docker/nginx-certs/default.key b/docker/nginx-certs/default.key index 93aad1c1..55bad668 100644 --- a/docker/nginx-certs/default.key +++ b/docker/nginx-certs/default.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyKKdx3GwoZuGB -VFkZTJTomPiBKRRIHdrvSatRAHOiBH0/t1tWO0a/4K6561duIN5XSFchsmAga1zB -6MdNJDiPyg0dE502DFWEfF8+QVHCUhN27CKU5TJpyd/F9NzQTquOXkPnCiFNZvTY -lVoD8QRdcj3cYDWqp2rYNogd+mhRL/qPwuBQCmCkZPmSX9QS2iHi951QDEjptgjZ -8Fin7oxLmV5v7pvmmVx5WRbdeUfWdllnlPc5j/O+ggl1XQRPL7aeAmWUAg96J0sf -wnO/tYrCQxYRPYTOYTQuRy+34gkOMJczz1E3e/3kzejiM0uRILwpxFCUFMlUapsg -fzs3K2zHAgMBAAECggEAb1FRlqZIelIWMw32AgXEwTTbiTCWuJzW8E7SmXEzzxcI -/2fczRuKkFeeNRLkdS5aXqUXtDlVMMyEj7CX3w5zvPxdhz57K4s3X+mqRSbhJA7O -lK7kyK4Q+uKlTHY8BngxX2ps+Q5uUoOzFyTysqXEuXKpTmpa/f9Pljs0f663wYmy -69+Vn4wi6kiU+OtvN5NDx4mWp3fX3vccfPz1Nwdis1HlC9a1G7CE5vNL5TDLo3oD -neh55wq8XhdqhQHxsZOa/vM8VfZzrwJeKkUyKL2dHrDiaUZXcSVv9OxBKqX3mk5A -HPY+yiMS5NyeHA7ac4kT4iWstGQiJ2y/z9sSoZDJYQKBgQDd+zTjjrkkecHy4mKS -odonUEMqVaHoAlAt79PpmTHlFpld7P72cjJqXf8wqz7F0X0+abzhyBDTQnl7OY6L -aGjPp9aYHc0l3R2QLF8188/3EgfCuRTQiDeA6uWJJxbGYjq/c1nVcYUx3dCvoKBB -PmOvBLESSFf8eqw6DsyAOo1rFwKBgQDNdjMXT7tpIEGEX9Pkx2ny5BfbGxVtuJXd -fVQ9xPg4iRAHbXApk0AQFkLU23UJhEDPxZjeM0UHCPXKXamoctANExo4mHRoJP/L -lrXkZEBqKBbCRFSJVzsRF6foDXOd2DEeW1+PdrTEPPTqVKrP8U2Bmx9EKNXcGeYl -BjO5eeRZ0QKBgQCjfhq1I3BvUhIsHtr1Hqo7XF0ruAhYhiOhwcr1eTSLgzqQODaN -Mwy41ORYZzgDoMi7CEvqi10BDCvHO1wmh07y6q4eOYzYP2Q/xL6XUSyxz6DRpVmU -QU+H7voCKz0V/lFAoufeUg8E7FeAkzCU/SuUQ0NbsNbFCWECHdY292kjHwKBgCJD -UNfVWwp375UUqfBf9OIleXj3dkZa3tsV2GOIomBDMyIZ9Pdp7+f+3lxC0KyqmmhL -qmd7o0o+C0cZAX7uzpUvl1LS0a7AZMvdsS2KLlfFoa352SMiId6C1GRVQe+TqvVG -BZSWFiUXiTw+rFGLCwLPDJLAQJG42/FWrG+EzTjRAoGBAMn3ieOjlhhV9vGmXMdJ -GD3nJCYC66B2VXH+VuPuLV0AFryVNmaCtLIXivDJu9i2v1NLQ1IijJfhUsrgEjyb -FXmA/9ybFU7wwHbNQ9orAOZ8CjDgGYo7+NL48C5yNonlGyVHvmsUGwDLtZ/fj508 -8NrWltAQfc4LboEde0sI0EDZ +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVBiNCVGdAB4N8 +UYry0bEYdLfmHB8PjfnTQ+XtcRsjjfK8YlDcWYyCYVF+lK7KUjw0I+N+8Q3bx9TP +F23YaUebMePyuE9tD7U0jA6bhZ6VXTN834cvJvaNIpIceOLZYNU+/i74wxfcmOHx +VMvN4h3rHfz/C/3Hn6+ZM4F52bYclhfrKFHBSdzbkyPJyPaaVk2oQMUOXKttB0Oo +bvrYVIjxN/W8+swz1I5ITIP8VgHpjUI97RKZ5QYunsG0uL1stXSfhwhOJH89xYwc +fo11EVkNji6giB/9ok/MEKGqgdcyo19gL6dN7euhJZzMaOMfhW/cCQeb1cFcTGuM +121TUBqHAgMBAAECggEAB/UCvCeK88lUEAC7v/Y1N0Sk2eOTBXG4MzwGCqh+6wUS +XBcQDisKJJSeBqxnGweXWBs/FC7M5bjBKjslzz+ffRyP9zELRnefvSa+JPEIy2t/ +0NpIompCK2NvMcESOCx1yrST7Jbc/VB4oBsawcYAeBfWq3A3Oo2scXyLCZIoS0j+ +fuEM+s1gg5+vqbYb2+0KUcth2pPRSnHzJXtu6nN5YICgnQpHXfhCyWV2XS36lbeN +m6+hRz4w+VJUAomfIo5ahe6LuYbZt77thOPgf1xWYU+GM+HQUytJPRFjfJPna39G +88SVxvqWAxlfb2tdVeIM6EiSpUr2ncgITylYFy7DgQKBgQD6PQhoK35Cw2SEwWNX +BnoeZvC4YmGJEK8fkp3hri/A3NJ3y0GvLQV8e5LGt9O1xgFp6PdxwpbJhyMy97sN +GxNQcxn8+Xa89JsQQGbjz7LHfUMKqVg+xq8E4a4rKauYOReVvr98grBxLdm0DZNO +Vb6ntbbtsEkO3mBniUDrfES4swKBgQDZ7cD5E+ArjvVjfOrlo6lV3FMDrrv8DwnA +ls1Kvs8MQ9+E4YZ01/+DZ3iCbeKH/NbKm4XXVoZEPA1nvulvdrTaGr7bowzKzHHh +hpz0mfzsKZxXeogemiDYQ6BQO3Z0lHQbTJJOZSMFkXp+agt2ikJOmTLXNxgAN8nq +KeYQAdS43QKBgB+SxdXG7xZjavJpKCyZz5y4ZlUNbLsLlN0J9cu825+c/R1KUw5U +QuXy/ZD/LsI3qoP/dgEviTECUQmkQkCkEurKqxPFMhsjTdFeHt1NnoQXJPdaaJz7 +GqgmBYDCsDjzsysctzJxluug2mAielye6wBkKCGTZZRvsIA/zCYqNs2LAoGBANNx +xnElIrTAoUClPDgRIkSHYBhLmmNGp/yvlII4PiW1WRLRyqZlyKlTZG6QdWHiJPky +CptTfTSJW6xUZKPcdj7EAniSa9/8m2XpOTJukiMFgIa0AYxHmSScANi3yQf13e16 +zt23bVKCw2oSNAsQvKMMK3L7JpNXjdZgTrMrQ50VAoGBAIqSg3w2wnjsHrqryGQa +jIUCm80EDx5t2tqGAn23RbR3ps+tSRB6KLjaZM+S90SzFRZjI3shA60tXTj2Mra/ +xcJpc828KgGnyZIRB2gmO/YFURwLEx3dOwWlTS8wfRb3inCGydwQu2A+V59CLmck +VTUEdI5qZrc1Aq/5OjkFI1GJ -----END PRIVATE KEY----- diff --git a/docker/nginx-certs/expiration b/docker/nginx-certs/expiration index 30455c25..8124a64c 100644 --- a/docker/nginx-certs/expiration +++ b/docker/nginx-certs/expiration @@ -1 +1 @@ -notAfter=Sep 5 17:12:39 2021 GMT +notAfter=Feb 28 22:58:22 2022 GMT diff --git a/docker/ssp/config-override.php b/docker/ssp/config-override.php index 1853eec6..d7dae4ea 100644 --- a/docker/ssp/config-override.php +++ b/docker/ssp/config-override.php @@ -1,5 +1,6 @@ 'testsalt', 'database.dsn' => 'sqlite:/var/simplesamlphp/data/mydb.sq3', diff --git a/docker/ssp/module_oidc.php b/docker/ssp/module_oidc.php index c3636308..01700c78 100644 --- a/docker/ssp/module_oidc.php +++ b/docker/ssp/module_oidc.php @@ -45,9 +45,6 @@ 'client' => ['urn:example:oidc:manage:client'], ], - 'alwaysAddClaimsToIdToken' => false, - 'alwaysIssueRefreshToken' => false, - // Settings regarding Authentication Processing Filters. // Note: OIDC authN state array will not contain all of the keys which are available during SAML authN, // like Service Provider metadata, etc. diff --git a/docker/ssp/oidc_module.pem b/docker/ssp/oidc_module.key similarity index 100% rename from docker/ssp/oidc_module.pem rename to docker/ssp/oidc_module.key diff --git a/hooks/hook_cron.php b/hooks/hook_cron.php index 8c5f83f9..35690d54 100644 --- a/hooks/hook_cron.php +++ b/hooks/hook_cron.php @@ -29,10 +29,10 @@ function oidc_hook_cron(&$croninfo) $oidcConfig = \SimpleSAML\Configuration::getConfig('module_oidc.php'); - if (null === $oidcConfig->getValue('cron_tag', 'hourly')) { + if (null === $oidcConfig->getOptionalValue('cron_tag', null)) { return; } - if ($oidcConfig->getValue('cron_tag', null) !== $croninfo['tag']) { + if ($oidcConfig->getOptionalValue('cron_tag', null) !== $croninfo['tag']) { return; } diff --git a/lib/Form/Controls/CsrfProtection.php b/lib/Form/Controls/CsrfProtection.php deleted file mode 100644 index 7b5ef3d4..00000000 --- a/lib/Form/Controls/CsrfProtection.php +++ /dev/null @@ -1,49 +0,0 @@ -getRules()->reset(); - $this->addRule(self::PROTECTION, $errorMessage); - } - - public function getToken(): string - { - $sessionHandler = SessionHandler::getSessionHandler(); - /** @var Session $session */ - $session = $sessionHandler->loadSession(); - - $token = $session->getData('form_csrf', 'token'); - - if (!$token) { - $token = Random::generate(); - $session->setData('form_csrf', 'token', $token); - } - - return $token ^ $session->getSessionId(); - } -} diff --git a/phpcs.xml b/phpcs.xml index 67fb3000..35b744f1 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -8,15 +8,15 @@ config-templates hooks - lib + src spec tests - www + public - www/assets/* + public/assets/* diff --git a/phpunit.xml b/phpunit.xml index b0c246fc..5390b395 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,7 +12,7 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> - ./lib + ./src diff --git a/psalm.xml b/psalm.xml index 69ed7afa..d1867929 100644 --- a/psalm.xml +++ b/psalm.xml @@ -2,27 +2,29 @@ - + - + - + + @@ -31,7 +33,6 @@ - @@ -42,6 +43,12 @@ + + + + + + diff --git a/www/admin-clients/delete.php b/public/admin-clients/delete.php similarity index 100% rename from www/admin-clients/delete.php rename to public/admin-clients/delete.php diff --git a/www/admin-clients/edit.php b/public/admin-clients/edit.php similarity index 100% rename from www/admin-clients/edit.php rename to public/admin-clients/edit.php diff --git a/www/admin-clients/index.php b/public/admin-clients/index.php similarity index 100% rename from www/admin-clients/index.php rename to public/admin-clients/index.php diff --git a/www/admin-clients/new.php b/public/admin-clients/new.php similarity index 100% rename from www/admin-clients/new.php rename to public/admin-clients/new.php diff --git a/www/admin-clients/reset.php b/public/admin-clients/reset.php similarity index 100% rename from www/admin-clients/reset.php rename to public/admin-clients/reset.php diff --git a/www/admin-clients/show.php b/public/admin-clients/show.php similarity index 100% rename from www/admin-clients/show.php rename to public/admin-clients/show.php diff --git a/www/assets/clipboard/LICENSE b/public/assets/clipboard/LICENSE similarity index 100% rename from www/assets/clipboard/LICENSE rename to public/assets/clipboard/LICENSE diff --git a/www/assets/clipboard/clipboard.min.js b/public/assets/clipboard/clipboard.min.js similarity index 100% rename from www/assets/clipboard/clipboard.min.js rename to public/assets/clipboard/clipboard.min.js diff --git a/www/assets/fomantic/LICENSE.md b/public/assets/fomantic/LICENSE.md similarity index 100% rename from www/assets/fomantic/LICENSE.md rename to public/assets/fomantic/LICENSE.md diff --git a/www/assets/fomantic/semantic.min.css b/public/assets/fomantic/semantic.min.css similarity index 100% rename from www/assets/fomantic/semantic.min.css rename to public/assets/fomantic/semantic.min.css diff --git a/www/assets/fomantic/semantic.min.js b/public/assets/fomantic/semantic.min.js similarity index 100% rename from www/assets/fomantic/semantic.min.js rename to public/assets/fomantic/semantic.min.js diff --git a/www/assets/fomantic/themes/default/assets/fonts/brand-icons.eot b/public/assets/fomantic/themes/default/assets/fonts/brand-icons.eot similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/brand-icons.eot rename to public/assets/fomantic/themes/default/assets/fonts/brand-icons.eot diff --git a/www/assets/fomantic/themes/default/assets/fonts/brand-icons.svg b/public/assets/fomantic/themes/default/assets/fonts/brand-icons.svg similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/brand-icons.svg rename to public/assets/fomantic/themes/default/assets/fonts/brand-icons.svg diff --git a/www/assets/fomantic/themes/default/assets/fonts/brand-icons.ttf b/public/assets/fomantic/themes/default/assets/fonts/brand-icons.ttf similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/brand-icons.ttf rename to public/assets/fomantic/themes/default/assets/fonts/brand-icons.ttf diff --git a/www/assets/fomantic/themes/default/assets/fonts/brand-icons.woff b/public/assets/fomantic/themes/default/assets/fonts/brand-icons.woff similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/brand-icons.woff rename to public/assets/fomantic/themes/default/assets/fonts/brand-icons.woff diff --git a/www/assets/fomantic/themes/default/assets/fonts/brand-icons.woff2 b/public/assets/fomantic/themes/default/assets/fonts/brand-icons.woff2 similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/brand-icons.woff2 rename to public/assets/fomantic/themes/default/assets/fonts/brand-icons.woff2 diff --git a/www/assets/fomantic/themes/default/assets/fonts/icons.eot b/public/assets/fomantic/themes/default/assets/fonts/icons.eot similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/icons.eot rename to public/assets/fomantic/themes/default/assets/fonts/icons.eot diff --git a/www/assets/fomantic/themes/default/assets/fonts/icons.svg b/public/assets/fomantic/themes/default/assets/fonts/icons.svg similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/icons.svg rename to public/assets/fomantic/themes/default/assets/fonts/icons.svg diff --git a/www/assets/fomantic/themes/default/assets/fonts/icons.ttf b/public/assets/fomantic/themes/default/assets/fonts/icons.ttf similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/icons.ttf rename to public/assets/fomantic/themes/default/assets/fonts/icons.ttf diff --git a/www/assets/fomantic/themes/default/assets/fonts/icons.woff b/public/assets/fomantic/themes/default/assets/fonts/icons.woff similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/icons.woff rename to public/assets/fomantic/themes/default/assets/fonts/icons.woff diff --git a/www/assets/fomantic/themes/default/assets/fonts/icons.woff2 b/public/assets/fomantic/themes/default/assets/fonts/icons.woff2 similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/icons.woff2 rename to public/assets/fomantic/themes/default/assets/fonts/icons.woff2 diff --git a/www/assets/fomantic/themes/default/assets/fonts/outline-icons.eot b/public/assets/fomantic/themes/default/assets/fonts/outline-icons.eot similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/outline-icons.eot rename to public/assets/fomantic/themes/default/assets/fonts/outline-icons.eot diff --git a/www/assets/fomantic/themes/default/assets/fonts/outline-icons.svg b/public/assets/fomantic/themes/default/assets/fonts/outline-icons.svg similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/outline-icons.svg rename to public/assets/fomantic/themes/default/assets/fonts/outline-icons.svg diff --git a/www/assets/fomantic/themes/default/assets/fonts/outline-icons.ttf b/public/assets/fomantic/themes/default/assets/fonts/outline-icons.ttf similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/outline-icons.ttf rename to public/assets/fomantic/themes/default/assets/fonts/outline-icons.ttf diff --git a/www/assets/fomantic/themes/default/assets/fonts/outline-icons.woff b/public/assets/fomantic/themes/default/assets/fonts/outline-icons.woff similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/outline-icons.woff rename to public/assets/fomantic/themes/default/assets/fonts/outline-icons.woff diff --git a/www/assets/fomantic/themes/default/assets/fonts/outline-icons.woff2 b/public/assets/fomantic/themes/default/assets/fonts/outline-icons.woff2 similarity index 100% rename from www/assets/fomantic/themes/default/assets/fonts/outline-icons.woff2 rename to public/assets/fomantic/themes/default/assets/fonts/outline-icons.woff2 diff --git a/www/assets/fomantic/themes/default/assets/images/flags.png b/public/assets/fomantic/themes/default/assets/images/flags.png similarity index 100% rename from www/assets/fomantic/themes/default/assets/images/flags.png rename to public/assets/fomantic/themes/default/assets/images/flags.png diff --git a/www/assets/jquery/LICENSE b/public/assets/jquery/LICENSE similarity index 100% rename from www/assets/jquery/LICENSE rename to public/assets/jquery/LICENSE diff --git a/www/assets/jquery/jquery-3.3.1.min.js b/public/assets/jquery/jquery-3.3.1.min.js similarity index 100% rename from www/assets/jquery/jquery-3.3.1.min.js rename to public/assets/jquery/jquery-3.3.1.min.js diff --git a/www/authorize.php b/public/authorize.php similarity index 100% rename from www/authorize.php rename to public/authorize.php diff --git a/www/clients/delete.php b/public/clients/delete.php similarity index 100% rename from www/clients/delete.php rename to public/clients/delete.php diff --git a/www/clients/edit.php b/public/clients/edit.php similarity index 100% rename from www/clients/edit.php rename to public/clients/edit.php diff --git a/www/clients/index.php b/public/clients/index.php similarity index 100% rename from www/clients/index.php rename to public/clients/index.php diff --git a/www/clients/new.php b/public/clients/new.php similarity index 100% rename from www/clients/new.php rename to public/clients/new.php diff --git a/www/clients/reset.php b/public/clients/reset.php similarity index 100% rename from www/clients/reset.php rename to public/clients/reset.php diff --git a/www/clients/show.php b/public/clients/show.php similarity index 100% rename from www/clients/show.php rename to public/clients/show.php diff --git a/www/install.php b/public/install.php similarity index 100% rename from www/install.php rename to public/install.php diff --git a/www/jwks.php b/public/jwks.php similarity index 100% rename from www/jwks.php rename to public/jwks.php diff --git a/www/logout.php b/public/logout.php similarity index 100% rename from www/logout.php rename to public/logout.php diff --git a/www/openid-configuration.php b/public/openid-configuration.php similarity index 100% rename from www/openid-configuration.php rename to public/openid-configuration.php diff --git a/www/token.php b/public/token.php similarity index 100% rename from www/token.php rename to public/token.php diff --git a/www/userinfo.php b/public/userinfo.php similarity index 100% rename from www/userinfo.php rename to public/userinfo.php diff --git a/routing/services/services.yml b/routing/services/services.yml index d60018ac..ee3f0201 100644 --- a/routing/services/services.yml +++ b/routing/services/services.yml @@ -4,18 +4,18 @@ services: public: false SimpleSAML\Module\oidc\Services\: - resource: '../../lib/Services/*' - exclude: '../../lib/Services/{Container.php}' + resource: '../../src/Services/*' + exclude: '../../src/Services/{Container.php}' SimpleSAML\Module\oidc\Repositories\: - resource: '../../lib/Repositories/*' - exclude: '../../lib/Repositories/{Interfaces}' + resource: '../../src/Repositories/*' + exclude: '../../src/Repositories/{Interfaces}' SimpleSAML\Module\oidc\Factories\: - resource: '../../lib/Factories/*' + resource: '../../src/Factories/*' SimpleSAML\Module\oidc\Store\: - resource: '../../lib/Store/*' + resource: '../../src/Store/*' SimpleSAML\Module\oidc\Server\AuthorizationServer: class: SimpleSAML\Module\oidc\Server\AuthorizationServer diff --git a/spec/Controller/ClientCreateControllerSpec.php b/spec/Controller/ClientCreateControllerSpec.php deleted file mode 100644 index 306958f6..00000000 --- a/spec/Controller/ClientCreateControllerSpec.php +++ /dev/null @@ -1,179 +0,0 @@ -getOpenIdConnectModuleURL(Argument::any())->willReturn("url"); - $authContextService->isSspAdmin()->willReturn(true); - $request->getUri()->willReturn($uri); - $uri->getPath()->willReturn('/'); - - $this->beConstructedWith( - $clientRepository, - $allowedOriginRepository, - $templateFactory, - $formFactory, - $sessionMessagesService, - $authContextService - ); - } - - /** - * @return void - */ - public function it_is_initializable() - { - $this->shouldHaveType(ClientCreateController::class); - } - - /** - * @return void - */ - public function it_shows_new_client_form( - ServerRequest $request, - Template $template, - TemplateFactory $templateFactory, - FormFactory $formFactory, - ClientForm $clientForm - ) { - $formFactory->build(ClientForm::class)->shouldBeCalled()->willReturn($clientForm); - $clientForm->setAction(Argument::any())->shouldBeCalled(); - $clientForm->isSuccess()->shouldBeCalled()->willReturn(false); - - $templateFactory->render('oidc:clients/new.twig', [ - 'form' => $clientForm, - 'regexUri' => ClientForm::REGEX_URI, - 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, - 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, - ]) - ->shouldBeCalled() - ->willReturn($template); - $this->__invoke($request)->shouldBe($template); - } - - /** - * @return void - */ - public function it_creates_new_client_from_form_data( - ServerRequest $request, - FormFactory $formFactory, - ClientForm $clientForm, - ClientRepository $clientRepository, - AllowedOriginRepository $allowedOriginRepository, - SessionMessagesService $sessionMessagesService - ) { - $formFactory->build(ClientForm::class)->shouldBeCalled()->willReturn($clientForm); - $clientForm->setAction(Argument::any())->shouldBeCalled(); - - $clientForm->isSuccess()->shouldBeCalled()->willReturn(true); - $clientForm->getValues()->shouldBeCalled()->willReturn([ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ]); - - $clientRepository->add(Argument::type(ClientEntity::class))->shouldBeCalled(); - - $allowedOriginRepository->set(Argument::type('string'), [])->shouldBeCalled(); - - $sessionMessagesService->addMessage('{oidc:client:added}')->shouldBeCalled(); - - $this->__invoke($request)->shouldBeAnInstanceOf(RedirectResponse::class); - } - - /** - * @return void - */ - public function it_owner_set_in_new_client( - ServerRequest $request, - FormFactory $formFactory, - ClientForm $clientForm, - ClientRepository $clientRepository, - SessionMessagesService $sessionMessagesService, - AuthContextService $authContextService - ) { - $authContextService->isSspAdmin()->shouldBeCalled()->willReturn(false); - $authContextService->getAuthUserId()->willReturn('ownerUsername'); - $formFactory->build(ClientForm::class)->shouldBeCalled()->willReturn($clientForm); - $clientForm->setAction(Argument::any())->shouldBeCalled(); - - $clientForm->isSuccess()->shouldBeCalled()->willReturn(true); - $clientForm->getValues()->shouldBeCalled()->willReturn( - [ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'wrongOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ] - ); - - $clientRepository->add(Argument::which('getOwner', 'ownerUsername'))->shouldBeCalled(); - $sessionMessagesService->addMessage('{oidc:client:added}')->shouldBeCalled(); - - $this->__invoke($request)->shouldBeAnInstanceOf(RedirectResponse::class); - } -} diff --git a/spec/Controller/ClientDeleteControllerSpec.php b/spec/Controller/ClientDeleteControllerSpec.php deleted file mode 100644 index 1ed3b727..00000000 --- a/spec/Controller/ClientDeleteControllerSpec.php +++ /dev/null @@ -1,188 +0,0 @@ -isSspAdmin()->willReturn(true); - - $request->getUri()->willReturn($uri); - $uri->getPath()->willReturn('/'); - - $this->beConstructedWith($clientRepository, $templateFactory, $sessionMessagesService, $authContextService); - } - - /** - * @return void - */ - public function it_is_initializable() - { - $this->shouldHaveType(ClientDeleteController::class); - } - - /** - * @return void - */ - public function it_asks_confirmation_before_delete_client( - ServerRequest $request, - Template $template, - TemplateFactory $templateFactory, - ClientRepository $clientRepository, - ClientEntity $clientEntity - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $request->getParsedBody()->shouldBeCalled()->willReturn([]); - $request->getMethod()->shouldBeCalled()->willReturn('get'); - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn($clientEntity); - - $templateFactory->render('oidc:clients/delete.twig', ['client' => $clientEntity]) - ->shouldBeCalled() - ->willReturn($template); - $this->__invoke($request)->shouldBe($template); - } - - /** - * @return void - */ - public function it_throws_id_not_found_exception_in_delete_action( - ServerRequest $request - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn([]); - - $this->shouldThrow(BadRequest::class)->during('__invoke', [$request]); - } - - /** - * @return void - */ - public function it_throws_client_not_found_exception_in_delete_action( - ServerRequest $request, - ClientRepository $clientRepository - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn(null); - - $this->shouldThrow(NotFound::class)->during('__invoke', [$request]); - } - - /** - * @return void - */ - public function it_throws_secret_not_found_exception_in_delete_action( - ServerRequest $request, - ClientRepository $clientRepository, - ClientEntity $clientEntity - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn($clientEntity); - $request->getParsedBody()->shouldBeCalled()->willReturn([]); - $request->getMethod()->shouldBeCalled()->willReturn('post'); - - $this->shouldThrow(BadRequest::class)->during('__invoke', [$request]); - } - - /** - * @return void - */ - public function it_throws_secret_invalid_exception_in_delete_action( - ServerRequest $request, - ClientRepository $clientRepository, - ClientEntity $clientEntity - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $request->getParsedBody()->shouldBeCalled()->willReturn(['secret' => 'invalidsecret']); - $request->getMethod()->shouldBeCalled()->willReturn('post'); - - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn($clientEntity); - $clientEntity->getSecret()->shouldBeCalled()->willReturn('validsecret'); - - $this->shouldThrow(BadRequest::class)->during('__invoke', [$request]); - } - - /** - * @return void - */ - public function it_deletes_client( - ServerRequest $request, - ClientRepository $clientRepository, - ClientEntity $clientEntity, - SessionMessagesService $sessionMessagesService - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $request->getParsedBody()->shouldBeCalled()->willReturn(['secret' => 'validsecret']); - $request->getMethod()->shouldBeCalled()->willReturn('post'); - - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn($clientEntity); - $clientEntity->getSecret()->shouldBeCalled()->willReturn('validsecret'); - $clientRepository->delete($clientEntity, null)->shouldBeCalled(); - - $sessionMessagesService->addMessage('{oidc:client:removed}')->shouldBeCalled(); - - $this->__invoke($request)->shouldBeAnInstanceOf(RedirectResponse::class); - } - - /** - * @return void - */ - public function it_deletes_client_with_owner( - ServerRequest $request, - ClientRepository $clientRepository, - ClientEntity $clientEntity, - SessionMessagesService $sessionMessagesService, - AuthContextService $authContextService - ) { - $authContextService->isSspAdmin()->shouldBeCalled()->willReturn(false); - $authContextService->getAuthUserId()->willReturn('theOwner'); - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $request->getParsedBody()->shouldBeCalled()->willReturn(['secret' => 'validsecret']); - $request->getMethod()->shouldBeCalled()->willReturn('post'); - - $clientRepository->findById('clientid', 'theOwner')->shouldBeCalled()->willReturn($clientEntity); - $clientEntity->getSecret()->shouldBeCalled()->willReturn('validsecret'); - $clientRepository->delete($clientEntity, 'theOwner')->shouldBeCalled(); - - $sessionMessagesService->addMessage('{oidc:client:removed}')->shouldBeCalled(); - - $this->__invoke($request)->shouldBeAnInstanceOf(RedirectResponse::class); - } -} diff --git a/spec/Controller/ClientEditControllerSpec.php b/spec/Controller/ClientEditControllerSpec.php deleted file mode 100644 index 12809ddb..00000000 --- a/spec/Controller/ClientEditControllerSpec.php +++ /dev/null @@ -1,307 +0,0 @@ -isSspAdmin()->willReturn(true); - $configurationService->getOpenIdConnectModuleURL()->willReturn("url"); - - $request->withQueryParams(Argument::any())->willReturn($request); - $request->getUri()->willReturn($uri); - $request->getRequestTarget()->willReturn('/'); - $uri->getPath()->willReturn('/'); - - $this->beConstructedWith( - $configurationService, - $clientRepository, - $allowedOriginRepository, - $templateFactory, - $formFactory, - $sessionMessagesService, - $authContextService - ); - } - - /** - * @return void - */ - public function it_is_initializable() - { - $this->shouldHaveType(ClientEditController::class); - } - - /** - * @return void - */ - public function it_shows_edit_client_form( - ServerRequest $request, - Template $template, - TemplateFactory $templateFactory, - FormFactory $formFactory, - ClientForm $clientForm, - ClientRepository $clientRepository, - AllowedOriginRepository $allowedOriginRepository, - ClientEntity $clientEntity - ) { - $data = [ - 'id' => 'clientid', - 'secret' => 'validsecret', - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ]; - $clientEntity->getIdentifier()->shouldBeCalled()->willReturn('clientid'); - - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn($clientEntity); - $allowedOriginRepository->get('clientid')->shouldBeCalled()->willReturn([]); - $clientEntity->toArray()->shouldBeCalled()->willReturn($data); - - $formFactory->build(ClientForm::class)->shouldBeCalled()->willReturn($clientForm); - $clientForm->setAction(Argument::any())->shouldBeCalled(); - $clientForm->setDefaults($data)->shouldBeCalled(); - - $clientForm->isSuccess()->shouldBeCalled()->willReturn(false); - - $templateFactory->render('oidc:clients/edit.twig', [ - 'form' => $clientForm, - 'regexUri' => ClientForm::REGEX_URI, - 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, - 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, - ]) - ->shouldBeCalled()->willReturn($template); - $this->__invoke($request)->shouldBe($template); - } - - /** - * @return void - */ - public function it_updates_client_from_edit_client_form_data( - ServerRequest $request, - FormFactory $formFactory, - ClientForm $clientForm, - ClientRepository $clientRepository, - AllowedOriginRepository $allowedOriginRepository, - ClientEntity $clientEntity, - SessionMessagesService $sessionMessagesService - ) { - $data = [ - 'id' => 'clientid', - 'secret' => 'validsecret', - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ]; - - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn($clientEntity); - $allowedOriginRepository->get('clientid')->shouldBeCalled()->willReturn([]); - $clientEntity->getIdentifier()->shouldBeCalled()->willReturn('clientid'); - $clientEntity->getSecret()->shouldBeCalled()->willReturn('validsecret'); - $clientEntity->getOwner()->shouldBeCalled()->willReturn('existingOwner'); - $clientEntity->toArray()->shouldBeCalled()->willReturn($data); - - $formFactory->build(ClientForm::class)->shouldBeCalled()->willReturn($clientForm); - $clientForm->setAction(Argument::any())->shouldBeCalled(); - $clientForm->isSuccess()->shouldBeCalled()->willReturn(true); - $clientForm->setDefaults($data)->shouldBeCalled(); - $clientForm->getValues()->shouldBeCalled()->willReturn([ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ]); - - $clientRepository->update(Argument::exact(ClientEntity::fromData( - 'clientid', - 'validsecret', - 'name', - 'description', - ['http://localhost/redirect'], - ['openid'], - true, - false, - 'auth_source', - 'existingOwner', - [] - )), null)->shouldBeCalled(); - - $allowedOriginRepository->set('clientid', [])->shouldBeCalled(); - $sessionMessagesService->addMessage('{oidc:client:updated}')->shouldBeCalled(); - - $this->__invoke($request)->shouldBeAnInstanceOf(RedirectResponse::class); - } - - /** - * @return void - */ - public function it_sends_owner_arg_to_repo_on_update( - ServerRequest $request, - FormFactory $formFactory, - ClientForm $clientForm, - ClientRepository $clientRepository, - ClientEntity $clientEntity, - SessionMessagesService $sessionMessagesService, - AuthContextService $authContextService, - AllowedOriginRepository $allowedOriginRepository - ) { - $authContextService->isSspAdmin()->shouldBeCalled()->willReturn(false); - $authContextService->getAuthUserId()->willReturn('authedUserId'); - $data = [ - 'id' => 'clientid', - 'secret' => 'validsecret', - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ]; - - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $clientRepository->findById('clientid', 'authedUserId')->shouldBeCalled()->willReturn($clientEntity); - $clientEntity->getIdentifier()->shouldBeCalled()->willReturn('clientid'); - $clientEntity->getSecret()->shouldBeCalled()->willReturn('validsecret'); - $clientEntity->getOwner()->shouldBeCalled()->willReturn('existingOwner'); - $clientEntity->toArray()->shouldBeCalled()->willReturn($data); - - $formFactory->build(ClientForm::class)->shouldBeCalled()->willReturn($clientForm); - $clientForm->setAction(Argument::any())->shouldBeCalled(); - $clientForm->isSuccess()->shouldBeCalled()->willReturn(true); - $clientForm->setDefaults($data)->shouldBeCalled(); - $clientForm->getValues()->shouldBeCalled()->willReturn([ - 'name' => 'name', - 'description' => 'description', - 'auth_source' => 'auth_source', - 'redirect_uri' => ['http://localhost/redirect'], - 'scopes' => ['openid'], - 'is_enabled' => true, - 'is_confidential' => false, - 'owner' => 'existingOwner', - 'allowed_origin' => [], - 'post_logout_redirect_uri' => [], - 'backchannel_logout_uri' => null, - ]); - - $clientRepository->update(Argument::exact(ClientEntity::fromData( - 'clientid', - 'validsecret', - 'name', - 'description', - ['http://localhost/redirect'], - ['openid'], - true, - false, - 'auth_source', - 'existingOwner', - [], - null - )), 'authedUserId')->shouldBeCalled(); - - $allowedOriginRepository->get('clientid')->shouldBeCalled()->willReturn([]); - $allowedOriginRepository->set('clientid', [])->shouldBeCalled(); - - $sessionMessagesService->addMessage('{oidc:client:updated}')->shouldBeCalled(); - - $this->__invoke($request)->shouldBeAnInstanceOf(RedirectResponse::class); - } - - /** - * @return void - */ - public function it_throws_id_not_found_exception_in_edit_action( - ServerRequest $request - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn([]); - - $this->shouldThrow(BadRequest::class)->during('__invoke', [$request]); - } - - /** - * @return void - */ - public function it_throws_client_not_found_exception_in_edit_action( - ServerRequest $request, - ClientRepository $clientRepository - ) { - $request->getQueryParams()->shouldBeCalled()->willReturn(['client_id' => 'clientid']); - $clientRepository->findById('clientid', null)->shouldBeCalled()->willReturn(null); - - $this->shouldThrow(NotFound::class)->during('__invoke', [$request]); - } -} diff --git a/spec/Controller/ClientIndexControllerSpec.php b/spec/Controller/ClientIndexControllerSpec.php deleted file mode 100644 index 1ff94b7e..00000000 --- a/spec/Controller/ClientIndexControllerSpec.php +++ /dev/null @@ -1,80 +0,0 @@ -isSspAdmin()->willReturn(true); - $request->getUri()->willReturn($uri); - $request->getQueryParams()->willReturn(['page' => 1]); - $uri->getPath()->willReturn('/'); - - $this->beConstructedWith($clientRepository, $templateFactory, $authContextService); - } - - /** - * @return void - */ - public function it_is_initializable() - { - $this->shouldHaveType(ClientIndexController::class); - } - - /** - * @return void - */ - public function it_shows_client_index( - ServerRequest $request, - Template $template, - TemplateFactory $templateFactory, - ClientRepository $clientRepository - ) { - $clientRepository->findPaginated(1, '', null)->shouldBeCalled()->willReturn([ - 'items' => [], - 'numPages' => 1, - 'currentPage' => 1 - ]); - $templateFactory->render('oidc:clients/index.twig', [ - 'clients' => [], - 'numPages' => 1, - 'currentPage' => 1, - 'query' => '', - ])->shouldBeCalled()->willReturn($template); - - $this->__invoke($request)->shouldBe($template); - } -} diff --git a/spec/Server/ResponseTypes/IdTokenResponseSpec.php b/spec/Server/ResponseTypes/IdTokenResponseSpec.php index ede2ec56..0098ea85 100644 --- a/spec/Server/ResponseTypes/IdTokenResponseSpec.php +++ b/spec/Server/ResponseTypes/IdTokenResponseSpec.php @@ -77,11 +77,11 @@ public function let( $configurationService->getSigner()->willReturn(new Sha256()); $configurationService->getSimpleSAMLSelfURLHost()->willReturn(self::ISSUER); $configurationService->getCertPath()->willReturn($this->certFolder . '/oidc_module.crt'); - $configurationService->getPrivateKeyPath()->willReturn($this->certFolder . '/oidc_module.pem'); + $configurationService->getPrivateKeyPath()->willReturn($this->certFolder . '/oidc_module.key'); $configurationService->getPrivateKeyPassPhrase()->shouldBeCalled(); $configurationService->getOpenIDConnectConfiguration()->willReturn($oidcConfig); - $privateKey = new CryptKey($this->certFolder . '/oidc_module.pem', null, false); + $privateKey = new CryptKey($this->certFolder . '/oidc_module.key', null, false); $idTokenBuilder = new IdTokenBuilder( new JsonWebTokenBuilderService($configurationService->getWrappedObject()), @@ -106,7 +106,6 @@ public function it_is_initializable() public function it_can_generate_response(AccessTokenEntity $accessToken, Configuration $oidcConfig) { - $oidcConfig->getBoolean('alwaysAddClaimsToIdToken', true)->willReturn(true); $response = new Response(); $this->setAccessToken($accessToken); $response = $this->generateHttpResponse($response); @@ -114,14 +113,13 @@ public function it_can_generate_response(AccessTokenEntity $accessToken, Configu $response->getBody()->rewind(); $body = $response->getBody()->getContents(); echo "json body response " . $body->getWrappedObject(); - $body->shouldHaveValidIdToken(['email' => 'myEmail@example.com']); + $body->shouldHaveValidIdToken(); } public function it_can_generate_response_with_no_token_claims( AccessTokenEntity $accessToken, Configuration $oidcConfig ) { - $oidcConfig->getBoolean('alwaysAddClaimsToIdToken', true)->willReturn(false); $response = new Response(); $this->setAccessToken($accessToken); $response = $this->generateHttpResponse($response); @@ -136,7 +134,6 @@ public function it_can_generate_response_with_individual_requested_claims( AccessTokenEntity $accessToken, Configuration $oidcConfig ) { - $oidcConfig->getBoolean('alwaysAddClaimsToIdToken', true)->willReturn(false); // ID token should only look at id_token for hints $accessToken->getRequestedClaims()->willReturn( [ diff --git a/spec/Services/AuthContextServiceSpec.php b/spec/Services/AuthContextServiceSpec.php index c1c26b04..de892008 100644 --- a/spec/Services/AuthContextServiceSpec.php +++ b/spec/Services/AuthContextServiceSpec.php @@ -35,7 +35,7 @@ public function let( 'client' => ['val2'], ] ); - $oidcConfiguration->getConfigItem('permissions', [])->willReturn($permssions); + $oidcConfiguration->getOptionalConfigItem('permissions', null)->willReturn($permssions); $this->beConstructedWith( $configurationService, $authSimpleFactory @@ -86,7 +86,7 @@ public function it_has_no_entitlment_attribute(Simple $simple) public function it_has_no_enable_permissions(Configuration $oidcConfiguration) { $permssions = Configuration::loadFromArray([]); - $oidcConfiguration->getConfigItem('permissions', [])->willReturn($permssions); + $oidcConfiguration->getOptionalConfigItem('permissions', null)->willReturn($permssions); $this->shouldThrow(new RuntimeException('Permissions not enabled'))->duringRequirePermission('client'); } } diff --git a/spec/Services/AuthenticationServiceSpec.php b/spec/Services/AuthenticationServiceSpec.php index 41e3a118..618627cf 100644 --- a/spec/Services/AuthenticationServiceSpec.php +++ b/spec/Services/AuthenticationServiceSpec.php @@ -135,6 +135,7 @@ public function it_creates_new_user( ClaimTranslatorExtractor $claimTranslatorExtractor ): void { $clientId = 'client123'; + $source->getAuthId()->willReturn('theAuthId'); $simple->isAuthenticated()->shouldBeCalled()->willReturn(false); $simple->login([])->shouldBeCalled(); $simple->getAuthSource()->shouldBeCalled()->willReturn($source); @@ -176,6 +177,7 @@ public function it_returns_an_user( ): void { $clientId = 'client123'; $userId = 'user123'; + $source->getAuthId()->willReturn('theAuthId'); $simple->isAuthenticated()->shouldBeCalled()->willReturn(false); $simple->login([])->shouldBeCalled(); $simple->getAuthSource()->shouldBeCalled()->willReturn($source); @@ -205,6 +207,7 @@ public function it_throws_exception_if_claims_not_exists( Source $source, SessionService $sessionService ): void { + $source->getAuthId()->willReturn('theAuthId'); $simple->isAuthenticated()->shouldBeCalled()->willReturn(false); $simple->login([])->shouldBeCalled(); $simple->getAuthSource()->shouldBeCalled()->willReturn($source); diff --git a/lib/ClaimTranslatorExtractor.php b/src/ClaimTranslatorExtractor.php similarity index 100% rename from lib/ClaimTranslatorExtractor.php rename to src/ClaimTranslatorExtractor.php diff --git a/lib/Controller/ClientCreateController.php b/src/Controller/ClientCreateController.php similarity index 93% rename from lib/Controller/ClientCreateController.php rename to src/Controller/ClientCreateController.php index 4d00152e..41119051 100644 --- a/lib/Controller/ClientCreateController.php +++ b/src/Controller/ClientCreateController.php @@ -72,8 +72,8 @@ public function __invoke(ServerRequest $request) if ($form->isSuccess()) { $client = $form->getValues(); - $client['id'] = Random::generateID(); - $client['secret'] = Random::generateID(); + $client['id'] = (new Random())->generateID(); + $client['secret'] = (new Random())->generateID(); if (!$this->authContextService->isSspAdmin()) { $client['owner'] = $this->authContextService->getAuthUserId(); } @@ -98,7 +98,7 @@ public function __invoke(ServerRequest $request) $this->messages->addMessage('{oidc:client:added}'); - return new RedirectResponse(HTTP::addURLParameters('show.php', ['client_id' => $client['id']])); + return new RedirectResponse((new HTTP())->addURLParameters('show.php', ['client_id' => $client['id']])); } return $this->templateFactory->render('oidc:clients/new.twig', [ diff --git a/lib/Controller/ClientDeleteController.php b/src/Controller/ClientDeleteController.php similarity index 96% rename from lib/Controller/ClientDeleteController.php rename to src/Controller/ClientDeleteController.php index add3a1de..73fa1b4b 100644 --- a/lib/Controller/ClientDeleteController.php +++ b/src/Controller/ClientDeleteController.php @@ -71,7 +71,7 @@ public function __invoke(ServerRequest $request) $this->clientRepository->delete($client, $authedUser); $this->messages->addMessage('{oidc:client:removed}'); - return new RedirectResponse(HTTP::addURLParameters('index.php', [])); + return new RedirectResponse((new HTTP())->addURLParameters('index.php', [])); } return $this->templateFactory->render('oidc:clients/delete.twig', [ diff --git a/lib/Controller/ClientEditController.php b/src/Controller/ClientEditController.php similarity index 96% rename from lib/Controller/ClientEditController.php rename to src/Controller/ClientEditController.php index decfe397..47800daf 100644 --- a/lib/Controller/ClientEditController.php +++ b/src/Controller/ClientEditController.php @@ -105,7 +105,9 @@ public function __invoke(ServerRequest $request) $this->messages->addMessage('{oidc:client:updated}'); - return new RedirectResponse(HTTP::addURLParameters('show.php', ['client_id' => $client->getIdentifier()])); + return new RedirectResponse( + (new HTTP())->addURLParameters('show.php', ['client_id' => $client->getIdentifier()]) + ); } return $this->templateFactory->render('oidc:clients/edit.twig', [ diff --git a/lib/Controller/ClientIndexController.php b/src/Controller/ClientIndexController.php similarity index 100% rename from lib/Controller/ClientIndexController.php rename to src/Controller/ClientIndexController.php diff --git a/lib/Controller/ClientResetSecretController.php b/src/Controller/ClientResetSecretController.php similarity index 91% rename from lib/Controller/ClientResetSecretController.php rename to src/Controller/ClientResetSecretController.php index ff101054..306b06f0 100644 --- a/lib/Controller/ClientResetSecretController.php +++ b/src/Controller/ClientResetSecretController.php @@ -59,12 +59,14 @@ public function __invoke(ServerRequest $request): RedirectResponse throw new BadRequest('Client secret is invalid.'); } - $client->restoreSecret(Random::generateID()); + $client->restoreSecret((new Random())->generateID()); $authedUser = $this->authContextService->isSspAdmin() ? null : $this->authContextService->getAuthUserId(); $this->clientRepository->update($client, $authedUser); $this->messages->addMessage('{oidc:client:secret_updated}'); } - return new RedirectResponse(HTTP::addURLParameters('show.php', ['client_id' => $client->getIdentifier()])); + return new RedirectResponse( + (new HTTP())->addURLParameters('show.php', ['client_id' => $client->getIdentifier()]) + ); } } diff --git a/lib/Controller/ClientShowController.php b/src/Controller/ClientShowController.php similarity index 100% rename from lib/Controller/ClientShowController.php rename to src/Controller/ClientShowController.php diff --git a/lib/Controller/LogoutController.php b/src/Controller/LogoutController.php similarity index 95% rename from lib/Controller/LogoutController.php rename to src/Controller/LogoutController.php index de4856bc..105cedba 100644 --- a/lib/Controller/LogoutController.php +++ b/src/Controller/LogoutController.php @@ -59,12 +59,7 @@ public function __invoke(ServerRequest $request): Response // TODO Back-Channel Logout: https://openid.net/specs/openid-connect-backchannel-1_0.html // [] Refresh tokens issued without the offline_access property to a session being logged out SHOULD // be revoked. Refresh tokens issued with the offline_access property normally SHOULD NOT be revoked. - // - currently we don't handle offline_access at all... - - // TODO Consider implementing Front-Channel Logout: - // https://openid.net/specs/openid-connect-frontchannel-1_0.html - // (FCL has challenges with User Agents Blocking Access to Third-Party Content: - // https://openid.net/specs/openid-connect-frontchannel-1_0.html#ThirdPartyContent) + // - offline_access scope is now handled. $logoutRequest = $this->authorizationServer->validateLogoutRequest($request); diff --git a/lib/Controller/OAuth2AccessTokenController.php b/src/Controller/OAuth2AccessTokenController.php similarity index 100% rename from lib/Controller/OAuth2AccessTokenController.php rename to src/Controller/OAuth2AccessTokenController.php diff --git a/lib/Controller/OAuth2AuthorizationController.php b/src/Controller/OAuth2AuthorizationController.php similarity index 100% rename from lib/Controller/OAuth2AuthorizationController.php rename to src/Controller/OAuth2AuthorizationController.php diff --git a/lib/Controller/OpenIdConnectDiscoverConfigurationController.php b/src/Controller/OpenIdConnectDiscoverConfigurationController.php similarity index 100% rename from lib/Controller/OpenIdConnectDiscoverConfigurationController.php rename to src/Controller/OpenIdConnectDiscoverConfigurationController.php diff --git a/lib/Controller/OpenIdConnectInstallerController.php b/src/Controller/OpenIdConnectInstallerController.php similarity index 92% rename from lib/Controller/OpenIdConnectInstallerController.php rename to src/Controller/OpenIdConnectInstallerController.php index 3aa79c7c..6f776e72 100644 --- a/lib/Controller/OpenIdConnectInstallerController.php +++ b/src/Controller/OpenIdConnectInstallerController.php @@ -63,7 +63,7 @@ public function __construct( public function __invoke(ServerRequest $request) { if ($this->databaseMigration->isUpdated()) { - return new RedirectResponse(HTTP::addURLParameters('admin-clients/index.php', [])); + return new RedirectResponse((new HTTP())->addURLParameters('admin-clients/index.php', [])); } $oauth2Enabled = \in_array('oauth2', Module::getModules(), true); @@ -78,7 +78,7 @@ public function __invoke(ServerRequest $request) $this->messages->addMessage('{oidc:import:finished}'); } - return new RedirectResponse(HTTP::addURLParameters('admin-clients/index.php', [])); + return new RedirectResponse((new HTTP())->addURLParameters('admin-clients/index.php', [])); } return $this->templateFactory->render('oidc:install.twig', [ diff --git a/lib/Controller/OpenIdConnectJwksController.php b/src/Controller/OpenIdConnectJwksController.php similarity index 100% rename from lib/Controller/OpenIdConnectJwksController.php rename to src/Controller/OpenIdConnectJwksController.php diff --git a/lib/Controller/OpenIdConnectUserInfoController.php b/src/Controller/OpenIdConnectUserInfoController.php similarity index 100% rename from lib/Controller/OpenIdConnectUserInfoController.php rename to src/Controller/OpenIdConnectUserInfoController.php diff --git a/lib/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php b/src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php similarity index 100% rename from lib/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php rename to src/Controller/Traits/AuthenticatedGetClientFromRequestTrait.php diff --git a/lib/Controller/Traits/GetClientFromRequestTrait.php b/src/Controller/Traits/GetClientFromRequestTrait.php similarity index 100% rename from lib/Controller/Traits/GetClientFromRequestTrait.php rename to src/Controller/Traits/GetClientFromRequestTrait.php diff --git a/lib/Entity/AccessTokenEntity.php b/src/Entity/AccessTokenEntity.php similarity index 100% rename from lib/Entity/AccessTokenEntity.php rename to src/Entity/AccessTokenEntity.php diff --git a/lib/Entity/AuthCodeEntity.php b/src/Entity/AuthCodeEntity.php similarity index 100% rename from lib/Entity/AuthCodeEntity.php rename to src/Entity/AuthCodeEntity.php diff --git a/lib/Entity/ClientEntity.php b/src/Entity/ClientEntity.php similarity index 100% rename from lib/Entity/ClientEntity.php rename to src/Entity/ClientEntity.php diff --git a/lib/Entity/Interfaces/AccessTokenEntityInterface.php b/src/Entity/Interfaces/AccessTokenEntityInterface.php similarity index 100% rename from lib/Entity/Interfaces/AccessTokenEntityInterface.php rename to src/Entity/Interfaces/AccessTokenEntityInterface.php diff --git a/lib/Entity/Interfaces/AuthCodeEntityInterface.php b/src/Entity/Interfaces/AuthCodeEntityInterface.php similarity index 100% rename from lib/Entity/Interfaces/AuthCodeEntityInterface.php rename to src/Entity/Interfaces/AuthCodeEntityInterface.php diff --git a/lib/Entity/Interfaces/ClientEntityInterface.php b/src/Entity/Interfaces/ClientEntityInterface.php similarity index 100% rename from lib/Entity/Interfaces/ClientEntityInterface.php rename to src/Entity/Interfaces/ClientEntityInterface.php diff --git a/lib/Entity/Interfaces/EntityStringRepresentationInterface.php b/src/Entity/Interfaces/EntityStringRepresentationInterface.php similarity index 100% rename from lib/Entity/Interfaces/EntityStringRepresentationInterface.php rename to src/Entity/Interfaces/EntityStringRepresentationInterface.php diff --git a/lib/Entity/Interfaces/MementoInterface.php b/src/Entity/Interfaces/MementoInterface.php similarity index 100% rename from lib/Entity/Interfaces/MementoInterface.php rename to src/Entity/Interfaces/MementoInterface.php diff --git a/lib/Entity/Interfaces/RefreshTokenEntityInterface.php b/src/Entity/Interfaces/RefreshTokenEntityInterface.php similarity index 100% rename from lib/Entity/Interfaces/RefreshTokenEntityInterface.php rename to src/Entity/Interfaces/RefreshTokenEntityInterface.php diff --git a/lib/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php b/src/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php similarity index 100% rename from lib/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php rename to src/Entity/Interfaces/TokenAssociatableWithAuthCodeInterface.php diff --git a/lib/Entity/Interfaces/TokenRevokableInterface.php b/src/Entity/Interfaces/TokenRevokableInterface.php similarity index 100% rename from lib/Entity/Interfaces/TokenRevokableInterface.php rename to src/Entity/Interfaces/TokenRevokableInterface.php diff --git a/lib/Entity/RefreshTokenEntity.php b/src/Entity/RefreshTokenEntity.php similarity index 100% rename from lib/Entity/RefreshTokenEntity.php rename to src/Entity/RefreshTokenEntity.php diff --git a/lib/Entity/ScopeEntity.php b/src/Entity/ScopeEntity.php similarity index 100% rename from lib/Entity/ScopeEntity.php rename to src/Entity/ScopeEntity.php diff --git a/lib/Entity/Traits/AssociateWithAuthCodeTrait.php b/src/Entity/Traits/AssociateWithAuthCodeTrait.php similarity index 100% rename from lib/Entity/Traits/AssociateWithAuthCodeTrait.php rename to src/Entity/Traits/AssociateWithAuthCodeTrait.php diff --git a/lib/Entity/Traits/OidcAuthCodeTrait.php b/src/Entity/Traits/OidcAuthCodeTrait.php similarity index 100% rename from lib/Entity/Traits/OidcAuthCodeTrait.php rename to src/Entity/Traits/OidcAuthCodeTrait.php diff --git a/lib/Entity/Traits/RevokeTokenTrait.php b/src/Entity/Traits/RevokeTokenTrait.php similarity index 100% rename from lib/Entity/Traits/RevokeTokenTrait.php rename to src/Entity/Traits/RevokeTokenTrait.php diff --git a/lib/Entity/UserEntity.php b/src/Entity/UserEntity.php similarity index 100% rename from lib/Entity/UserEntity.php rename to src/Entity/UserEntity.php diff --git a/lib/Factories/AuthSimpleFactory.php b/src/Factories/AuthSimpleFactory.php similarity index 100% rename from lib/Factories/AuthSimpleFactory.php rename to src/Factories/AuthSimpleFactory.php diff --git a/lib/Factories/AuthorizationServerFactory.php b/src/Factories/AuthorizationServerFactory.php similarity index 100% rename from lib/Factories/AuthorizationServerFactory.php rename to src/Factories/AuthorizationServerFactory.php diff --git a/lib/Factories/ClaimTranslatorExtractorFactory.php b/src/Factories/ClaimTranslatorExtractorFactory.php similarity index 98% rename from lib/Factories/ClaimTranslatorExtractorFactory.php rename to src/Factories/ClaimTranslatorExtractorFactory.php index 15467a84..af96b7c0 100644 --- a/lib/Factories/ClaimTranslatorExtractorFactory.php +++ b/src/Factories/ClaimTranslatorExtractorFactory.php @@ -40,7 +40,8 @@ public function __construct( */ public function build(): ClaimTranslatorExtractor { - $translatorTable = $this->configurationService->getOpenIDConnectConfiguration()->getArray('translate', []); + $translatorTable = $this->configurationService->getOpenIDConnectConfiguration() + ->getOptionalArray('translate', []); $privateScopes = $this->configurationService->getOpenIDPrivateScopes(); diff --git a/lib/Factories/CryptKeyFactory.php b/src/Factories/CryptKeyFactory.php similarity index 100% rename from lib/Factories/CryptKeyFactory.php rename to src/Factories/CryptKeyFactory.php diff --git a/lib/Factories/FormFactory.php b/src/Factories/FormFactory.php similarity index 100% rename from lib/Factories/FormFactory.php rename to src/Factories/FormFactory.php diff --git a/lib/Factories/Grant/AuthCodeGrantFactory.php b/src/Factories/Grant/AuthCodeGrantFactory.php similarity index 100% rename from lib/Factories/Grant/AuthCodeGrantFactory.php rename to src/Factories/Grant/AuthCodeGrantFactory.php diff --git a/lib/Factories/Grant/ImplicitGrantFactory.php b/src/Factories/Grant/ImplicitGrantFactory.php similarity index 100% rename from lib/Factories/Grant/ImplicitGrantFactory.php rename to src/Factories/Grant/ImplicitGrantFactory.php diff --git a/lib/Factories/Grant/OAuth2ImplicitGrantFactory.php b/src/Factories/Grant/OAuth2ImplicitGrantFactory.php similarity index 100% rename from lib/Factories/Grant/OAuth2ImplicitGrantFactory.php rename to src/Factories/Grant/OAuth2ImplicitGrantFactory.php diff --git a/lib/Factories/Grant/RefreshTokenGrantFactory.php b/src/Factories/Grant/RefreshTokenGrantFactory.php similarity index 100% rename from lib/Factories/Grant/RefreshTokenGrantFactory.php rename to src/Factories/Grant/RefreshTokenGrantFactory.php diff --git a/lib/Factories/IdTokenResponseFactory.php b/src/Factories/IdTokenResponseFactory.php similarity index 100% rename from lib/Factories/IdTokenResponseFactory.php rename to src/Factories/IdTokenResponseFactory.php diff --git a/lib/Factories/ResourceServerFactory.php b/src/Factories/ResourceServerFactory.php similarity index 100% rename from lib/Factories/ResourceServerFactory.php rename to src/Factories/ResourceServerFactory.php diff --git a/lib/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php similarity index 100% rename from lib/Factories/TemplateFactory.php rename to src/Factories/TemplateFactory.php diff --git a/lib/Form/ClientForm.php b/src/Form/ClientForm.php similarity index 77% rename from lib/Form/ClientForm.php rename to src/Form/ClientForm.php index d2420bc2..a9ae469c 100644 --- a/lib/Form/ClientForm.php +++ b/src/Form/ClientForm.php @@ -21,6 +21,8 @@ class ClientForm extends Form { + protected const TYPE_ARRAY = 'array'; + /** * RFC3986. AppendixB. Parsing a URI Reference with a Regular Expression. */ @@ -55,8 +57,11 @@ public function __construct(ConfigurationService $configurationService) public function validateRedirectUri(Form $form): void { + /** @var array $values */ + $values = $form->getValues(self::TYPE_ARRAY); + $this->validateByMatchingRegex( - $form->getValues()['redirect_uri'], + $values['redirect_uri'] ?? [], self::REGEX_URI, 'Invalid URI: ' ); @@ -64,8 +69,11 @@ public function validateRedirectUri(Form $form): void public function validateAllowedOrigin(Form $form): void { + /** @var array $values */ + $values = $form->getValues(self::TYPE_ARRAY); + $this->validateByMatchingRegex( - $form->getValues()['allowed_origin'] ?? [], + $values['allowed_origin'] ?? [], self::REGEX_ALLOWED_ORIGIN_URL, 'Invalid allowed origin: ' ); @@ -73,8 +81,11 @@ public function validateAllowedOrigin(Form $form): void public function validatePostLogoutRedirectUri(Form $form): void { + /** @var array $values */ + $values = $form->getValues(self::TYPE_ARRAY); + $this->validateByMatchingRegex( - $form->getValues()['post_logout_redirect_uri'] ?? [], + $values['post_logout_redirect_uri'] ?? [], self::REGEX_URI, 'Invalid post-logout redirect URI: ' ); @@ -103,14 +114,10 @@ protected function validateByMatchingRegex( } } - /** - * @param bool $asArray - * - * @return array - */ - public function getValues($asArray = false): array + public function getValues($returnType = null, ?array $controls = null): array { - $values = parent::getValues(true); + /** @var array $values */ + $values = parent::getValues(self::TYPE_ARRAY); // Sanitize redirect_uri and allowed_origin $values['redirect_uri'] = $this->convertTextToArrayWithLinesAsValues($values['redirect_uri']); @@ -136,37 +143,43 @@ public function getValues($asArray = false): array return $values; } - /** - * @param array $values - * @param bool $erase - * - * @return Form - */ - public function setDefaults($values, $erase = false): Form + public function setDefaults($data, bool $erase = false) { - $values['redirect_uri'] = implode("\n", $values['redirect_uri']); + if (! is_array($data)) { + if ($data instanceof \Traversable) { + $data = iterator_to_array($data); + } else { + $data = (array) $data; + } + } + + $data['redirect_uri'] = implode("\n", $data['redirect_uri']); // Allowed origins are only available for public clients (not for confidential clients). - if (! $values['is_confidential'] && isset($values['allowed_origin'])) { - $values['allowed_origin'] = implode("\n", $values['allowed_origin']); + if (! $data['is_confidential'] && isset($data['allowed_origin'])) { + $data['allowed_origin'] = implode("\n", $data['allowed_origin']); } else { - $values['allowed_origin'] = ''; + $data['allowed_origin'] = ''; } - $values['post_logout_redirect_uri'] = implode("\n", $values['post_logout_redirect_uri']); + $data['post_logout_redirect_uri'] = implode("\n", $data['post_logout_redirect_uri']); - $values['scopes'] = array_intersect($values['scopes'], array_keys($this->getScopes())); + $data['scopes'] = array_intersect($data['scopes'], array_keys($this->getScopes())); - return parent::setDefaults($values, $erase); + return parent::setDefaults($data, $erase); } protected function buildForm(): void { $this->getElementPrototype()->addAttributes(['class' => 'ui form']); + /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ $this->onValidate[] = [$this, 'validateRedirectUri']; + /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ $this->onValidate[] = [$this, 'validateAllowedOrigin']; + /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ $this->onValidate[] = [$this, 'validatePostLogoutRedirectUri']; + /** @psalm-suppress InvalidPropertyAssignmentValue According to docs this is fine. */ $this->onValidate[] = [$this, 'validateBackChannelLogoutUri']; $this->setMethod('POST'); @@ -185,14 +198,14 @@ protected function buildForm(): void $this->addCheckbox('is_confidential', '{oidc:client:is_confidential}'); $this->addSelect('auth_source', '{oidc:client:auth_source}:') - ->setAttribute('class', 'ui fluid dropdown clearable') + ->setHtmlAttribute('class', 'ui fluid dropdown clearable') ->setItems(Source::getSources(), false) ->setPrompt('Pick an AuthSource'); $scopes = $this->getScopes(); $this->addMultiSelect('scopes', '{oidc:client:scopes}') - ->setAttribute('class', 'ui fluid dropdown') + ->setHtmlAttribute('class', 'ui fluid dropdown') ->setItems($scopes) ->setRequired('Select one scope at least'); diff --git a/src/Form/Controls/CsrfProtection.php b/src/Form/Controls/CsrfProtection.php new file mode 100644 index 00000000..24e8054e --- /dev/null +++ b/src/Form/Controls/CsrfProtection.php @@ -0,0 +1,58 @@ +setOmitted() + ->setRequired() + ->addRule(self::PROTECTION, $errorMessage); + + $this->sspSession = Session::getSessionFromRequest(); + } + + public function getToken(): string + { + $token = $this->sspSession->getData('form_csrf', 'token'); + + if (!$token) { + $token = Random::generate(); + $this->sspSession->setData('form_csrf', 'token', $token); + } + + return $token ^ $this->sspSession->getSessionId(); + } +} diff --git a/lib/Repositories/AbstractDatabaseRepository.php b/src/Repositories/AbstractDatabaseRepository.php similarity index 100% rename from lib/Repositories/AbstractDatabaseRepository.php rename to src/Repositories/AbstractDatabaseRepository.php diff --git a/lib/Repositories/AccessTokenRepository.php b/src/Repositories/AccessTokenRepository.php similarity index 98% rename from lib/Repositories/AccessTokenRepository.php rename to src/Repositories/AccessTokenRepository.php index 3662b5e0..7115b266 100644 --- a/lib/Repositories/AccessTokenRepository.php +++ b/src/Repositories/AccessTokenRepository.php @@ -16,7 +16,7 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface as OAuth2AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface; -use SimpleSAML\Error\Assertion; +use SimpleSAML\Error\Error; use SimpleSAML\Module\oidc\Entity\AccessTokenEntity; use SimpleSAML\Module\oidc\Entity\Interfaces\AccessTokenEntityInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; @@ -56,7 +56,7 @@ public function getNewToken( public function persistNewAccessToken(OAuth2AccessTokenEntityInterface $accessTokenEntity) { if (!$accessTokenEntity instanceof AccessTokenEntity) { - throw new Assertion('Invalid AccessTokenEntity'); + throw new Error('Invalid AccessTokenEntity'); } $stmt = sprintf( diff --git a/lib/Repositories/AllowedOriginRepository.php b/src/Repositories/AllowedOriginRepository.php similarity index 100% rename from lib/Repositories/AllowedOriginRepository.php rename to src/Repositories/AllowedOriginRepository.php diff --git a/lib/Repositories/AuthCodeRepository.php b/src/Repositories/AuthCodeRepository.php similarity index 90% rename from lib/Repositories/AuthCodeRepository.php rename to src/Repositories/AuthCodeRepository.php index ef4e881e..40e6f13a 100644 --- a/lib/Repositories/AuthCodeRepository.php +++ b/src/Repositories/AuthCodeRepository.php @@ -15,12 +15,10 @@ namespace SimpleSAML\Module\oidc\Repositories; use League\OAuth2\Server\Entities\AuthCodeEntityInterface as OAuth2AuthCodeEntityInterface; -use SimpleSAML\Error\Assertion; +use SimpleSAML\Error\Error; use SimpleSAML\Module\oidc\Entity\AuthCodeEntity; use SimpleSAML\Module\oidc\Entity\Interfaces\AuthCodeEntityInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface; -use SimpleSAML\Module\oidc\Entity\Interfaces\OidcAuthCodeEntityInterface; -use SimpleSAML\Module\oidc\Repositories\Interfaces\OidcAuthCodeRepositoryInterface; use SimpleSAML\Module\oidc\Utils\TimestampGenerator; class AuthCodeRepository extends AbstractDatabaseRepository implements AuthCodeRepositoryInterface @@ -46,7 +44,7 @@ public function getNewAuthCode(): AuthCodeEntityInterface public function persistNewAuthCode(OAuth2AuthCodeEntityInterface $authCodeEntity) { if (!$authCodeEntity instanceof AuthCodeEntity) { - throw new Assertion('Invalid AuthCodeEntity'); + throw new Error('Invalid AuthCodeEntity'); } $stmt = sprintf( @@ -102,12 +100,12 @@ public function revokeAuthCode($codeId) /** * {@inheritdoc} */ - public function isAuthCodeRevoked($tokenId): bool + public function isAuthCodeRevoked($codeId): bool { - $authCode = $this->findById($tokenId); + $authCode = $this->findById($codeId); if (!$authCode instanceof AuthCodeEntity) { - throw new \RuntimeException("AuthCode not found: {$tokenId}"); + throw new \RuntimeException("AuthCode not found: {$codeId}"); } return $authCode->isRevoked(); diff --git a/lib/Repositories/ClientRepository.php b/src/Repositories/ClientRepository.php similarity index 99% rename from lib/Repositories/ClientRepository.php rename to src/Repositories/ClientRepository.php index d291d23f..28921796 100644 --- a/lib/Repositories/ClientRepository.php +++ b/src/Repositories/ClientRepository.php @@ -273,7 +273,7 @@ private function count(string $query, ?string $owner): int */ private function getItemsPerPage(): int { - return $this->config->getIntegerRange('items_per_page', 1, 100, 20); + return $this->config->getOptionalIntegerRange('items_per_page', 1, 100, 20); } /** diff --git a/lib/Repositories/CodeChallengeVerifiersRepository.php b/src/Repositories/CodeChallengeVerifiersRepository.php similarity index 100% rename from lib/Repositories/CodeChallengeVerifiersRepository.php rename to src/Repositories/CodeChallengeVerifiersRepository.php diff --git a/lib/Repositories/Interfaces/AccessTokenRepositoryInterface.php b/src/Repositories/Interfaces/AccessTokenRepositoryInterface.php similarity index 100% rename from lib/Repositories/Interfaces/AccessTokenRepositoryInterface.php rename to src/Repositories/Interfaces/AccessTokenRepositoryInterface.php diff --git a/lib/Repositories/Interfaces/AuthCodeRepositoryInterface.php b/src/Repositories/Interfaces/AuthCodeRepositoryInterface.php similarity index 100% rename from lib/Repositories/Interfaces/AuthCodeRepositoryInterface.php rename to src/Repositories/Interfaces/AuthCodeRepositoryInterface.php diff --git a/lib/Repositories/Interfaces/RefreshTokenRepositoryInterface.php b/src/Repositories/Interfaces/RefreshTokenRepositoryInterface.php similarity index 100% rename from lib/Repositories/Interfaces/RefreshTokenRepositoryInterface.php rename to src/Repositories/Interfaces/RefreshTokenRepositoryInterface.php diff --git a/lib/Repositories/RefreshTokenRepository.php b/src/Repositories/RefreshTokenRepository.php similarity index 100% rename from lib/Repositories/RefreshTokenRepository.php rename to src/Repositories/RefreshTokenRepository.php diff --git a/lib/Repositories/ScopeRepository.php b/src/Repositories/ScopeRepository.php similarity index 100% rename from lib/Repositories/ScopeRepository.php rename to src/Repositories/ScopeRepository.php diff --git a/lib/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php b/src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php similarity index 100% rename from lib/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php rename to src/Repositories/Traits/RevokeTokenByAuthCodeIdTrait.php diff --git a/lib/Repositories/UserRepository.php b/src/Repositories/UserRepository.php similarity index 100% rename from lib/Repositories/UserRepository.php rename to src/Repositories/UserRepository.php diff --git a/lib/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php b/src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php similarity index 100% rename from lib/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php rename to src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php diff --git a/lib/Server/Associations/RelyingPartyAssociation.php b/src/Server/Associations/RelyingPartyAssociation.php similarity index 100% rename from lib/Server/Associations/RelyingPartyAssociation.php rename to src/Server/Associations/RelyingPartyAssociation.php diff --git a/lib/Server/AuthorizationServer.php b/src/Server/AuthorizationServer.php similarity index 100% rename from lib/Server/AuthorizationServer.php rename to src/Server/AuthorizationServer.php diff --git a/lib/Server/Exceptions/OidcServerException.php b/src/Server/Exceptions/OidcServerException.php similarity index 100% rename from lib/Server/Exceptions/OidcServerException.php rename to src/Server/Exceptions/OidcServerException.php diff --git a/lib/Server/Grants/AuthCodeGrant.php b/src/Server/Grants/AuthCodeGrant.php similarity index 95% rename from lib/Server/Grants/AuthCodeGrant.php rename to src/Server/Grants/AuthCodeGrant.php index 7f398796..b8e6a185 100644 --- a/lib/Server/Grants/AuthCodeGrant.php +++ b/src/Server/Grants/AuthCodeGrant.php @@ -25,7 +25,6 @@ use SimpleSAML\Module\oidc\Entity\Interfaces\ClientEntityInterface; use SimpleSAML\Module\oidc\Entity\Interfaces\RefreshTokenEntityInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\AccessTokenRepositoryInterface; -use SimpleSAML\Module\oidc\Repositories\Interfaces\AuthCodeRepositoryInterface; use SimpleSAML\Module\oidc\Repositories\Interfaces\RefreshTokenRepositoryInterface; use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Server\Grants\Interfaces\AuthorizationValidatableWithCheckerResultBagInterface; @@ -71,19 +70,10 @@ class AuthCodeGrant extends OAuth2AuthCodeGrant implements */ protected array $codeChallengeVerifiers = []; - /** - * @var AuthCodeRepositoryInterface - */ protected $authCodeRepository; - /** - * @var AccessTokenRepositoryInterface - */ protected $accessTokenRepository; - /** - * @var RefreshTokenRepositoryInterface - */ protected $refreshTokenRepository; private RequestRulesManager $requestRulesManager; @@ -120,10 +110,10 @@ public function __construct( } /** - * @param ClientEntityInterface $client + * @param OAuth2ClientEntityInterface $client * @return bool */ - protected function shouldCheckPkce(ClientEntityInterface $client): bool + protected function shouldCheckPkce(OAuth2ClientEntityInterface $client): bool { return $this->requireCodeChallengeForPublicClients && ! $client->isConfidential(); @@ -449,17 +439,8 @@ public function respondToAccessTokenRequest( $responseType->setSessionId($authCodePayload->session_id); } - // Refresh token should only be released if the client requests it using the 'offline_access' scope. - // However, for module backwards compatibility we have enabled the deployer to explicitly state that - // the refresh token should always be released. - // @see https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess - // TODO in v3 remove this config option and do as per spec. - $alwaysIssueRefreshToken = $this->configurationService - ->getOpenIDConnectConfiguration() - ->getBoolean('alwaysIssueRefreshToken', true); - - // Release refresh token per deployer config or if it is requested by using offline_access scope. - if ($alwaysIssueRefreshToken || ScopeHelper::scopeExists($scopes, 'offline_access')) { + // Release refresh token if it is requested by using offline_access scope. + if (ScopeHelper::scopeExists($scopes, 'offline_access')) { // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken, $authCodePayload->auth_code_id); diff --git a/lib/Server/Grants/ImplicitGrant.php b/src/Server/Grants/ImplicitGrant.php similarity index 99% rename from lib/Server/Grants/ImplicitGrant.php rename to src/Server/Grants/ImplicitGrant.php index fca781df..3b39e17d 100644 --- a/lib/Server/Grants/ImplicitGrant.php +++ b/src/Server/Grants/ImplicitGrant.php @@ -236,10 +236,10 @@ private function getRedirectUrl(AuthorizationRequest $authorizationRequest): str $redirectUris = $authorizationRequest->getClient()->getRedirectUri(); if (is_array($redirectUris)) { - return (string) $redirectUris[0]; + return $redirectUris[0]; } - return (string) $redirectUris; + return $redirectUris; } /** diff --git a/lib/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php b/src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php similarity index 100% rename from lib/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php rename to src/Server/Grants/Interfaces/AuthorizationValidatableWithCheckerResultBagInterface.php diff --git a/lib/Server/Grants/Interfaces/OidcCapableGrantTypeInterface.php b/src/Server/Grants/Interfaces/OidcCapableGrantTypeInterface.php similarity index 100% rename from lib/Server/Grants/Interfaces/OidcCapableGrantTypeInterface.php rename to src/Server/Grants/Interfaces/OidcCapableGrantTypeInterface.php diff --git a/lib/Server/Grants/Interfaces/PkceEnabledGrantTypeInterface.php b/src/Server/Grants/Interfaces/PkceEnabledGrantTypeInterface.php similarity index 100% rename from lib/Server/Grants/Interfaces/PkceEnabledGrantTypeInterface.php rename to src/Server/Grants/Interfaces/PkceEnabledGrantTypeInterface.php diff --git a/lib/Server/Grants/OAuth2ImplicitGrant.php b/src/Server/Grants/OAuth2ImplicitGrant.php similarity index 100% rename from lib/Server/Grants/OAuth2ImplicitGrant.php rename to src/Server/Grants/OAuth2ImplicitGrant.php diff --git a/lib/Server/Grants/RefreshTokenGrant.php b/src/Server/Grants/RefreshTokenGrant.php similarity index 100% rename from lib/Server/Grants/RefreshTokenGrant.php rename to src/Server/Grants/RefreshTokenGrant.php diff --git a/lib/Server/Grants/Traits/IssueAccessTokenTrait.php b/src/Server/Grants/Traits/IssueAccessTokenTrait.php similarity index 98% rename from lib/Server/Grants/Traits/IssueAccessTokenTrait.php rename to src/Server/Grants/Traits/IssueAccessTokenTrait.php index e7eaf9b1..3f057be1 100644 --- a/lib/Server/Grants/Traits/IssueAccessTokenTrait.php +++ b/src/Server/Grants/Traits/IssueAccessTokenTrait.php @@ -22,9 +22,6 @@ */ trait IssueAccessTokenTrait { - /** - * @var AccessTokenRepositoryInterface - */ protected $accessTokenRepository; /** diff --git a/lib/Server/LogoutHandlers/BackChannelLogoutHandler.php b/src/Server/LogoutHandlers/BackChannelLogoutHandler.php similarity index 100% rename from lib/Server/LogoutHandlers/BackChannelLogoutHandler.php rename to src/Server/LogoutHandlers/BackChannelLogoutHandler.php diff --git a/lib/Server/RequestTypes/AuthorizationRequest.php b/src/Server/RequestTypes/AuthorizationRequest.php similarity index 100% rename from lib/Server/RequestTypes/AuthorizationRequest.php rename to src/Server/RequestTypes/AuthorizationRequest.php diff --git a/lib/Server/RequestTypes/LogoutRequest.php b/src/Server/RequestTypes/LogoutRequest.php similarity index 100% rename from lib/Server/RequestTypes/LogoutRequest.php rename to src/Server/RequestTypes/LogoutRequest.php diff --git a/lib/Server/ResponseTypes/IdTokenResponse.php b/src/Server/ResponseTypes/IdTokenResponse.php similarity index 91% rename from lib/Server/ResponseTypes/IdTokenResponse.php rename to src/Server/ResponseTypes/IdTokenResponse.php index b4ee11a0..e98ab5d7 100644 --- a/lib/Server/ResponseTypes/IdTokenResponse.php +++ b/src/Server/ResponseTypes/IdTokenResponse.php @@ -81,12 +81,6 @@ protected function getExtraParams(AccessTokenEntityInterface $accessToken): arra if ($accessToken instanceof AccessTokenEntity === false) { throw new RuntimeException('AccessToken must be ' . AccessTokenEntity::class); } - // Per https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.4 certain claims - // should only be added in certain scenarios. Allow deployer to control this. - // TODO in v3 remove this config option and do as per spec. - $addClaimsFromScopes = $this->configurationService - ->getOpenIDConnectConfiguration() - ->getBoolean('alwaysAddClaimsToIdToken', true); /** @var UserEntityInterface $userEntity */ $userEntity = $this->identityProvider->getUserEntityByIdentifier($accessToken->getUserIdentifier()); @@ -94,7 +88,7 @@ protected function getExtraParams(AccessTokenEntityInterface $accessToken): arra $token = $this->idTokenBuilder->build( $userEntity, $accessToken, - $addClaimsFromScopes, + false, true, $this->getNonce(), $this->getAuthTime(), diff --git a/lib/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php b/src/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php similarity index 100% rename from lib/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php rename to src/Server/ResponseTypes/Interfaces/AcrResponseTypeInterface.php diff --git a/lib/Server/ResponseTypes/Interfaces/AuthTimeResponseTypeInterface.php b/src/Server/ResponseTypes/Interfaces/AuthTimeResponseTypeInterface.php similarity index 100% rename from lib/Server/ResponseTypes/Interfaces/AuthTimeResponseTypeInterface.php rename to src/Server/ResponseTypes/Interfaces/AuthTimeResponseTypeInterface.php diff --git a/lib/Server/ResponseTypes/Interfaces/NonceResponseTypeInterface.php b/src/Server/ResponseTypes/Interfaces/NonceResponseTypeInterface.php similarity index 100% rename from lib/Server/ResponseTypes/Interfaces/NonceResponseTypeInterface.php rename to src/Server/ResponseTypes/Interfaces/NonceResponseTypeInterface.php diff --git a/lib/Server/ResponseTypes/Interfaces/SessionIdResponseTypeInterface.php b/src/Server/ResponseTypes/Interfaces/SessionIdResponseTypeInterface.php similarity index 100% rename from lib/Server/ResponseTypes/Interfaces/SessionIdResponseTypeInterface.php rename to src/Server/ResponseTypes/Interfaces/SessionIdResponseTypeInterface.php diff --git a/lib/Server/Validators/BearerTokenValidator.php b/src/Server/Validators/BearerTokenValidator.php similarity index 98% rename from lib/Server/Validators/BearerTokenValidator.php rename to src/Server/Validators/BearerTokenValidator.php index 889c6dce..e1af4c49 100644 --- a/lib/Server/Validators/BearerTokenValidator.php +++ b/src/Server/Validators/BearerTokenValidator.php @@ -66,6 +66,7 @@ protected function initJwtConfiguration() InMemory::empty() ); + /** @psalm-suppress ArgumentTypeCoercion */ $this->jwtConfiguration->setValidationConstraints( new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith( diff --git a/lib/Services/AuthContextService.php b/src/Services/AuthContextService.php similarity index 88% rename from lib/Services/AuthContextService.php rename to src/Services/AuthContextService.php index d0d796e2..e2a9e74a 100644 --- a/lib/Services/AuthContextService.php +++ b/src/Services/AuthContextService.php @@ -41,14 +41,14 @@ public function __construct(ConfigurationService $configurationService, AuthSimp public function isSspAdmin(): bool { - return Auth::isAdmin(); + return (new Auth())->isAdmin(); } public function getAuthUserId(): string { $simple = $this->authenticate(); $userIdAttr = $this->configurationService->getOpenIDConnectConfiguration()->getString('useridattr'); - return Attributes::getExpectedAttribute($simple->getAttributes(), $userIdAttr); + return (new Attributes())->getExpectedAttribute($simple->getAttributes(), $userIdAttr); } /** @@ -60,7 +60,10 @@ public function requirePermission(string $neededPermission) { $auth = $this->authenticate(); - $permissions = $this->configurationService->getOpenIDConnectConfiguration()->getConfigItem('permissions', []); + $permissions = $this->configurationService + ->getOpenIDConnectConfiguration() + ->getOptionalConfigItem('permissions', null); + /** @psalm-suppress DocblockTypeContradiction */ if (is_null($permissions) || !$permissions->hasValue('attribute')) { throw new \RuntimeException('Permissions not enabled'); } diff --git a/lib/Services/AuthProcService.php b/src/Services/AuthProcService.php similarity index 100% rename from lib/Services/AuthProcService.php rename to src/Services/AuthProcService.php diff --git a/lib/Services/AuthenticationService.php b/src/Services/AuthenticationService.php similarity index 100% rename from lib/Services/AuthenticationService.php rename to src/Services/AuthenticationService.php diff --git a/lib/Services/ConfigurationService.php b/src/Services/ConfigurationService.php similarity index 87% rename from lib/Services/ConfigurationService.php rename to src/Services/ConfigurationService.php index aad2a6a8..15ae9a74 100644 --- a/lib/Services/ConfigurationService.php +++ b/src/Services/ConfigurationService.php @@ -74,7 +74,7 @@ public function getOpenIDConnectConfiguration(): Configuration public function getSimpleSAMLSelfURLHost(): string { - return HTTP::getSelfURLHost(); + return (new HTTP())->getSelfURLHost(); } public function getOpenIdConnectModuleURL(string $path = null): string @@ -101,7 +101,7 @@ public function getOpenIDScopes(): array */ public function getOpenIDPrivateScopes(): array { - return $this->getOpenIDConnectConfiguration()->getArray('scopes', []); + return $this->getOpenIDConnectConfiguration()->getOptionalArray('scopes', []); } /** @@ -177,7 +177,7 @@ function (array $scope, string $name): void { public function getSigner(): Signer { /** @psalm-var class-string $signerClassname */ - $signerClassname = (string) $this->getOpenIDConnectConfiguration()->getString('signer', Sha256::class); + $signerClassname = $this->getOpenIDConnectConfiguration()->getOptionalString('signer', Sha256::class); $class = new ReflectionClass($signerClassname); $signer = $class->newInstance(); @@ -195,8 +195,8 @@ public function getSigner(): Signer */ public function getCertPath(): string { - $certName = $this->getOpenIDConnectConfiguration()->getString('certificate', 'oidc_module.crt'); - return Config::getCertPath($certName); + $certName = $this->getOpenIDConnectConfiguration()->getOptionalString('certificate', 'oidc_module.crt'); + return (new Config())->getCertPath($certName); } /** @@ -205,8 +205,8 @@ public function getCertPath(): string */ public function getPrivateKeyPath(): string { - $keyName = $this->getOpenIDConnectConfiguration()->getString('privatekey', 'oidc_module.pem'); - return Config::getCertPath($keyName); + $keyName = $this->getOpenIDConnectConfiguration()->getOptionalString('privatekey', 'oidc_module.key'); + return (new Config())->getCertPath($keyName); } /** @@ -216,7 +216,7 @@ public function getPrivateKeyPath(): string */ public function getPrivateKeyPassPhrase(): ?string { - return $this->getOpenIDConnectConfiguration()->getString('pass_phrase', null); + return $this->getOpenIDConnectConfiguration()->getOptionalString('pass_phrase', null); } /** @@ -227,7 +227,7 @@ public function getPrivateKeyPassPhrase(): ?string */ public function getAuthProcFilters(): array { - return $this->getOpenIDConnectConfiguration()->getArray('authproc.oidc', []); + return $this->getOpenIDConnectConfiguration()->getOptionalArray('authproc.oidc', []); } /** @@ -238,7 +238,7 @@ public function getAuthProcFilters(): array */ public function getAcrValuesSupported(): array { - return array_values($this->getOpenIDConnectConfiguration()->getArray('acrValuesSupported', [])); + return array_values($this->getOpenIDConnectConfiguration()->getOptionalArray('acrValuesSupported', [])); } /** @@ -249,7 +249,7 @@ public function getAcrValuesSupported(): array */ public function getAuthSourcesToAcrValuesMap(): array { - return $this->getOpenIDConnectConfiguration()->getArray('authSourcesToAcrValuesMap', []); + return $this->getOpenIDConnectConfiguration()->getOptionalArray('authSourcesToAcrValuesMap', []); } /** @@ -259,7 +259,7 @@ public function getAuthSourcesToAcrValuesMap(): array public function getForcedAcrValueForCookieAuthentication(): ?string { $value = $this->getOpenIDConnectConfiguration() - ->getValue('forcedAcrValueForCookieAuthentication'); + ->getOptionalValue('forcedAcrValueForCookieAuthentication', null); if (is_null($value)) { return null; diff --git a/lib/Services/Container.php b/src/Services/Container.php similarity index 99% rename from lib/Services/Container.php rename to src/Services/Container.php index 60110bd0..7e2fccaf 100644 --- a/lib/Services/Container.php +++ b/src/Services/Container.php @@ -169,7 +169,7 @@ public function __construct() $oidcOpenIdProviderMetadataService, $sessionService, $claimTranslatorExtractor, - $oidcModuleConfiguration->getString('useridattr', 'uid') + $oidcModuleConfiguration->getOptionalString('useridattr', 'uid') ); $this->services[AuthenticationService::class] = $authenticationService; @@ -221,7 +221,7 @@ public function __construct() ); $publicKey = $cryptKeyFactory->buildPublicKey(); $privateKey = $cryptKeyFactory->buildPrivateKey(); - $encryptionKey = Config::getSecretSalt(); + $encryptionKey = (new Config())->getSecretSalt(); $jsonWebTokenBuilderService = new JsonWebTokenBuilderService($configurationService); $this->services[JsonWebTokenBuilderService::class] = $jsonWebTokenBuilderService; diff --git a/lib/Services/DatabaseLegacyOAuth2Import.php b/src/Services/DatabaseLegacyOAuth2Import.php similarity index 100% rename from lib/Services/DatabaseLegacyOAuth2Import.php rename to src/Services/DatabaseLegacyOAuth2Import.php diff --git a/lib/Services/DatabaseMigration.php b/src/Services/DatabaseMigration.php similarity index 100% rename from lib/Services/DatabaseMigration.php rename to src/Services/DatabaseMigration.php diff --git a/lib/Services/IdTokenBuilder.php b/src/Services/IdTokenBuilder.php similarity index 98% rename from lib/Services/IdTokenBuilder.php rename to src/Services/IdTokenBuilder.php index 0ea00c39..6e68873d 100644 --- a/lib/Services/IdTokenBuilder.php +++ b/src/Services/IdTokenBuilder.php @@ -158,7 +158,7 @@ protected function generateAccessTokenHash(AccessTokenEntityInterface $accessTok $hashAlgorithm = 'sha' . $jwsAlgorithmBitLength; - $hashByteLength = (int) ($jwsAlgorithmBitLength / 2 / 8); + $hashByteLength = $jwsAlgorithmBitLength / 2 / 8; return Base64Url::encode( substr( diff --git a/lib/Services/JsonWebKeySetService.php b/src/Services/JsonWebKeySetService.php similarity index 99% rename from lib/Services/JsonWebKeySetService.php rename to src/Services/JsonWebKeySetService.php index 63eae2f3..450766b7 100644 --- a/lib/Services/JsonWebKeySetService.php +++ b/src/Services/JsonWebKeySetService.php @@ -34,7 +34,6 @@ class JsonWebKeySetService public function __construct(ConfigurationService $configurationService) { $publicKeyPath = $configurationService->getCertPath(); - if (!file_exists($publicKeyPath)) { throw new Exception("OpenId Connect certification file does not exists: {$publicKeyPath}."); } diff --git a/lib/Services/JsonWebTokenBuilderService.php b/src/Services/JsonWebTokenBuilderService.php similarity index 100% rename from lib/Services/JsonWebTokenBuilderService.php rename to src/Services/JsonWebTokenBuilderService.php diff --git a/lib/Services/LoggerService.php b/src/Services/LoggerService.php similarity index 100% rename from lib/Services/LoggerService.php rename to src/Services/LoggerService.php diff --git a/lib/Services/LogoutTokenBuilder.php b/src/Services/LogoutTokenBuilder.php similarity index 100% rename from lib/Services/LogoutTokenBuilder.php rename to src/Services/LogoutTokenBuilder.php diff --git a/lib/Services/OidcOpenIdProviderMetadataService.php b/src/Services/OidcOpenIdProviderMetadataService.php similarity index 100% rename from lib/Services/OidcOpenIdProviderMetadataService.php rename to src/Services/OidcOpenIdProviderMetadataService.php diff --git a/lib/Services/RoutingService.php b/src/Services/RoutingService.php similarity index 99% rename from lib/Services/RoutingService.php rename to src/Services/RoutingService.php index ff4fd77f..4727214d 100644 --- a/lib/Services/RoutingService.php +++ b/src/Services/RoutingService.php @@ -41,7 +41,7 @@ class RoutingService public static function call(string $controllerClassname, bool $authenticated = true, bool $jsonResponse = false) { if ($authenticated) { - Auth::requireAdmin(); + (new Auth())->requireAdmin(); } if ($jsonResponse) { diff --git a/lib/Services/SessionMessagesService.php b/src/Services/SessionMessagesService.php similarity index 100% rename from lib/Services/SessionMessagesService.php rename to src/Services/SessionMessagesService.php diff --git a/lib/Services/SessionService.php b/src/Services/SessionService.php similarity index 100% rename from lib/Services/SessionService.php rename to src/Services/SessionService.php diff --git a/lib/Store/SessionLogoutTicketStoreBuilder.php b/src/Store/SessionLogoutTicketStoreBuilder.php similarity index 100% rename from lib/Store/SessionLogoutTicketStoreBuilder.php rename to src/Store/SessionLogoutTicketStoreBuilder.php diff --git a/lib/Store/SessionLogoutTicketStoreDb.php b/src/Store/SessionLogoutTicketStoreDb.php similarity index 100% rename from lib/Store/SessionLogoutTicketStoreDb.php rename to src/Store/SessionLogoutTicketStoreDb.php diff --git a/lib/Store/SessionLogoutTicketStoreInterface.php b/src/Store/SessionLogoutTicketStoreInterface.php similarity index 100% rename from lib/Store/SessionLogoutTicketStoreInterface.php rename to src/Store/SessionLogoutTicketStoreInterface.php diff --git a/lib/Utils/Arr.php b/src/Utils/Arr.php similarity index 100% rename from lib/Utils/Arr.php rename to src/Utils/Arr.php diff --git a/lib/Utils/Checker/Interfaces/RequestRuleInterface.php b/src/Utils/Checker/Interfaces/RequestRuleInterface.php similarity index 100% rename from lib/Utils/Checker/Interfaces/RequestRuleInterface.php rename to src/Utils/Checker/Interfaces/RequestRuleInterface.php diff --git a/lib/Utils/Checker/Interfaces/ResultBagInterface.php b/src/Utils/Checker/Interfaces/ResultBagInterface.php similarity index 100% rename from lib/Utils/Checker/Interfaces/ResultBagInterface.php rename to src/Utils/Checker/Interfaces/ResultBagInterface.php diff --git a/lib/Utils/Checker/Interfaces/ResultInterface.php b/src/Utils/Checker/Interfaces/ResultInterface.php similarity index 100% rename from lib/Utils/Checker/Interfaces/ResultInterface.php rename to src/Utils/Checker/Interfaces/ResultInterface.php diff --git a/lib/Utils/Checker/RequestRulesManager.php b/src/Utils/Checker/RequestRulesManager.php similarity index 100% rename from lib/Utils/Checker/RequestRulesManager.php rename to src/Utils/Checker/RequestRulesManager.php diff --git a/lib/Utils/Checker/Result.php b/src/Utils/Checker/Result.php similarity index 100% rename from lib/Utils/Checker/Result.php rename to src/Utils/Checker/Result.php diff --git a/lib/Utils/Checker/ResultBag.php b/src/Utils/Checker/ResultBag.php similarity index 100% rename from lib/Utils/Checker/ResultBag.php rename to src/Utils/Checker/ResultBag.php diff --git a/lib/Utils/Checker/Rules/AbstractRule.php b/src/Utils/Checker/Rules/AbstractRule.php similarity index 100% rename from lib/Utils/Checker/Rules/AbstractRule.php rename to src/Utils/Checker/Rules/AbstractRule.php diff --git a/lib/Utils/Checker/Rules/AcrValuesRule.php b/src/Utils/Checker/Rules/AcrValuesRule.php similarity index 100% rename from lib/Utils/Checker/Rules/AcrValuesRule.php rename to src/Utils/Checker/Rules/AcrValuesRule.php diff --git a/lib/Utils/Checker/Rules/AddClaimsToIdTokenRule.php b/src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php similarity index 100% rename from lib/Utils/Checker/Rules/AddClaimsToIdTokenRule.php rename to src/Utils/Checker/Rules/AddClaimsToIdTokenRule.php diff --git a/lib/Utils/Checker/Rules/ClientIdRule.php b/src/Utils/Checker/Rules/ClientIdRule.php similarity index 100% rename from lib/Utils/Checker/Rules/ClientIdRule.php rename to src/Utils/Checker/Rules/ClientIdRule.php diff --git a/lib/Utils/Checker/Rules/CodeChallengeMethodRule.php b/src/Utils/Checker/Rules/CodeChallengeMethodRule.php similarity index 100% rename from lib/Utils/Checker/Rules/CodeChallengeMethodRule.php rename to src/Utils/Checker/Rules/CodeChallengeMethodRule.php diff --git a/lib/Utils/Checker/Rules/CodeChallengeRule.php b/src/Utils/Checker/Rules/CodeChallengeRule.php similarity index 100% rename from lib/Utils/Checker/Rules/CodeChallengeRule.php rename to src/Utils/Checker/Rules/CodeChallengeRule.php diff --git a/lib/Utils/Checker/Rules/IdTokenHintRule.php b/src/Utils/Checker/Rules/IdTokenHintRule.php similarity index 96% rename from lib/Utils/Checker/Rules/IdTokenHintRule.php rename to src/Utils/Checker/Rules/IdTokenHintRule.php index cb488c9c..8e9bb631 100644 --- a/lib/Utils/Checker/Rules/IdTokenHintRule.php +++ b/src/Utils/Checker/Rules/IdTokenHintRule.php @@ -58,6 +58,7 @@ public function checkRule( $privateKey = $this->cryptKeyFactory->buildPrivateKey(); $publicKey = $this->cryptKeyFactory->buildPublicKey(); + /** @psalm-suppress ArgumentTypeCoercion */ $jwtConfig = Configuration::forAsymmetricSigner( $this->configurationService->getSigner(), InMemory::plainText($privateKey->getKeyContents(), $privateKey->getPassPhrase() ?? ''), @@ -68,6 +69,7 @@ public function checkRule( /** @var UnencryptedToken $idTokenHint */ $idTokenHint = $jwtConfig->parser()->parse($idTokenHintParam); + /** @psalm-suppress ArgumentTypeCoercion */ $jwtConfig->validator()->assert( $idTokenHint, new IssuedBy($this->configurationService->getSimpleSAMLSelfURLHost()), diff --git a/lib/Utils/Checker/Rules/MaxAgeRule.php b/src/Utils/Checker/Rules/MaxAgeRule.php similarity index 93% rename from lib/Utils/Checker/Rules/MaxAgeRule.php rename to src/Utils/Checker/Rules/MaxAgeRule.php index 1d440b89..76a8c45d 100644 --- a/lib/Utils/Checker/Rules/MaxAgeRule.php +++ b/src/Utils/Checker/Rules/MaxAgeRule.php @@ -75,10 +75,10 @@ public function checkRule( $isExpired = $lastAuth + $maxAge < time(); if ($isExpired) { - $queryParams = HTTP::parseQueryString($request->getUri()->getQuery()); + $queryParams = (new HTTP())->parseQueryString($request->getUri()->getQuery()); unset($queryParams['prompt']); $loginParams = []; - $loginParams['ReturnTo'] = HTTP::addURLParameters(HTTP::getSelfURLNoQuery(), $queryParams); + $loginParams['ReturnTo'] = (new HTTP())->addURLParameters((new HTTP())->getSelfURLNoQuery(), $queryParams); $this->authenticationService->getAuthenticateUser($request, $loginParams, true); } diff --git a/lib/Utils/Checker/Rules/PostLogoutRedirectUriRule.php b/src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php similarity index 100% rename from lib/Utils/Checker/Rules/PostLogoutRedirectUriRule.php rename to src/Utils/Checker/Rules/PostLogoutRedirectUriRule.php diff --git a/lib/Utils/Checker/Rules/PromptRule.php b/src/Utils/Checker/Rules/PromptRule.php similarity index 93% rename from lib/Utils/Checker/Rules/PromptRule.php rename to src/Utils/Checker/Rules/PromptRule.php index 2f6758ec..db115032 100644 --- a/lib/Utils/Checker/Rules/PromptRule.php +++ b/src/Utils/Checker/Rules/PromptRule.php @@ -74,10 +74,10 @@ public function checkRule( } if (in_array('login', $prompt, true) && $authSimple->isAuthenticated()) { - $queryParams = HTTP::parseQueryString($request->getUri()->getQuery()); + $queryParams = (new HTTP())->parseQueryString($request->getUri()->getQuery()); unset($queryParams['prompt']); $loginParams = []; - $loginParams['ReturnTo'] = HTTP::addURLParameters(HTTP::getSelfURLNoQuery(), $queryParams); + $loginParams['ReturnTo'] = (new HTTP())->addURLParameters((new HTTP())->getSelfURLNoQuery(), $queryParams); $this->authenticationService->getAuthenticateUser($request, $loginParams, true); } diff --git a/lib/Utils/Checker/Rules/RedirectUriRule.php b/src/Utils/Checker/Rules/RedirectUriRule.php similarity index 100% rename from lib/Utils/Checker/Rules/RedirectUriRule.php rename to src/Utils/Checker/Rules/RedirectUriRule.php diff --git a/lib/Utils/Checker/Rules/RequestParameterRule.php b/src/Utils/Checker/Rules/RequestParameterRule.php similarity index 100% rename from lib/Utils/Checker/Rules/RequestParameterRule.php rename to src/Utils/Checker/Rules/RequestParameterRule.php diff --git a/lib/Utils/Checker/Rules/RequestedClaimsRule.php b/src/Utils/Checker/Rules/RequestedClaimsRule.php similarity index 100% rename from lib/Utils/Checker/Rules/RequestedClaimsRule.php rename to src/Utils/Checker/Rules/RequestedClaimsRule.php diff --git a/lib/Utils/Checker/Rules/RequiredNonceRule.php b/src/Utils/Checker/Rules/RequiredNonceRule.php similarity index 100% rename from lib/Utils/Checker/Rules/RequiredNonceRule.php rename to src/Utils/Checker/Rules/RequiredNonceRule.php diff --git a/lib/Utils/Checker/Rules/RequiredOpenIdScopeRule.php b/src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php similarity index 100% rename from lib/Utils/Checker/Rules/RequiredOpenIdScopeRule.php rename to src/Utils/Checker/Rules/RequiredOpenIdScopeRule.php diff --git a/lib/Utils/Checker/Rules/ResponseTypeRule.php b/src/Utils/Checker/Rules/ResponseTypeRule.php similarity index 100% rename from lib/Utils/Checker/Rules/ResponseTypeRule.php rename to src/Utils/Checker/Rules/ResponseTypeRule.php diff --git a/lib/Utils/Checker/Rules/ScopeOfflineAccessRule.php b/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php similarity index 75% rename from lib/Utils/Checker/Rules/ScopeOfflineAccessRule.php rename to src/Utils/Checker/Rules/ScopeOfflineAccessRule.php index 036c446a..462b398a 100644 --- a/lib/Utils/Checker/Rules/ScopeOfflineAccessRule.php +++ b/src/Utils/Checker/Rules/ScopeOfflineAccessRule.php @@ -42,19 +42,6 @@ public function checkRule( /** @var ScopeEntityInterface[] $validScopes */ $validScopes = $currentResultBag->getOrFail(ScopeRule::class)->getValue(); - // Refresh token should only be released if the client requests it using the 'offline_access' scope. - // However, for module backwards compatibility we have enabled the deployer to explicitly state that - // the refresh token should always be released. - // @see https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess - // TODO in v3 remove this config option and do as per spec. - $alwaysIssueRefreshToken = $this->configurationService - ->getOpenIDConnectConfiguration() - ->getBoolean('alwaysIssueRefreshToken', true); - // If the deployer decided to always issue refresh token, we don't have to check offline_access scope. - if ($alwaysIssueRefreshToken) { - return new Result($this->getKey(), true); - } - // Check if offline_access scope is used. If not, we don't have to check anything else. if (! ScopeHelper::scopeExists($validScopes, 'offline_access')) { return new Result($this->getKey(), false); diff --git a/lib/Utils/Checker/Rules/ScopeRule.php b/src/Utils/Checker/Rules/ScopeRule.php similarity index 93% rename from lib/Utils/Checker/Rules/ScopeRule.php rename to src/Utils/Checker/Rules/ScopeRule.php index 89ccb6db..18c77230 100644 --- a/lib/Utils/Checker/Rules/ScopeRule.php +++ b/src/Utils/Checker/Rules/ScopeRule.php @@ -68,9 +68,14 @@ public function checkRule( * @param string $scopes * @param string $scopeDelimiterString * @return array + * @throws OidcServerException */ protected function convertScopesQueryStringToArray(string $scopes, string $scopeDelimiterString): array { + if (empty($scopeDelimiterString)) { + throw OidcServerException::serverError('Scope delimiter string can not be empty.'); + } + return array_filter(explode($scopeDelimiterString, trim($scopes)), function ($scope) { return !empty($scope); }); diff --git a/lib/Utils/Checker/Rules/StateRule.php b/src/Utils/Checker/Rules/StateRule.php similarity index 100% rename from lib/Utils/Checker/Rules/StateRule.php rename to src/Utils/Checker/Rules/StateRule.php diff --git a/lib/Utils/Checker/Rules/UiLocalesRule.php b/src/Utils/Checker/Rules/UiLocalesRule.php similarity index 100% rename from lib/Utils/Checker/Rules/UiLocalesRule.php rename to src/Utils/Checker/Rules/UiLocalesRule.php diff --git a/lib/Utils/FingerprintGenerator.php b/src/Utils/FingerprintGenerator.php similarity index 100% rename from lib/Utils/FingerprintGenerator.php rename to src/Utils/FingerprintGenerator.php diff --git a/lib/Utils/ScopeHelper.php b/src/Utils/ScopeHelper.php similarity index 100% rename from lib/Utils/ScopeHelper.php rename to src/Utils/ScopeHelper.php diff --git a/lib/Utils/TimestampGenerator.php b/src/Utils/TimestampGenerator.php similarity index 100% rename from lib/Utils/TimestampGenerator.php rename to src/Utils/TimestampGenerator.php diff --git a/lib/Utils/UniqueIdentifierGenerator.php b/src/Utils/UniqueIdentifierGenerator.php similarity index 82% rename from lib/Utils/UniqueIdentifierGenerator.php rename to src/Utils/UniqueIdentifierGenerator.php index 86217112..108c6cf7 100644 --- a/lib/Utils/UniqueIdentifierGenerator.php +++ b/src/Utils/UniqueIdentifierGenerator.php @@ -17,6 +17,10 @@ class UniqueIdentifierGenerator */ public static function hitMe(int $length = 40): string { + if ($length < 1) { + throw OidcServerException::serverError('Random string lenght can not be less than 1'); + } + try { return bin2hex(random_bytes($length)); } catch (Throwable $e) { diff --git a/templates/clients/_form.twig b/templates/clients/_form.twig index dd41f040..4eca53c2 100644 --- a/templates/clients/_form.twig +++ b/templates/clients/_form.twig @@ -17,13 +17,13 @@ {% endif %}
- + {{ form['name'].control | raw }}
- + {{ form['description'].control | raw }}
@@ -32,7 +32,7 @@
- +
@@ -40,50 +40,50 @@
- - {% trans '{oidc:client:is_confidential_help}' %} + + {{ '{oidc:client:is_confidential_help}'|trans }}
- + {{ form['redirect_uri'].control | raw }} - {% trans '{oidc:client:redirect_uri_help}' %} + {{ '{oidc:client:redirect_uri_help}'|trans }}
- + {{ form['auth_source'].control | raw }} - {% trans '{oidc:client:auth_source_help}' %} + {{ '{oidc:client:auth_source_help}'|trans }}
- + {{ form['scopes'].control | raw }}
- + {{ form['backchannel_logout_uri'].control | raw }} - {% trans '{oidc:client:backchannel_logout_uri_help}' %} + {{ '{oidc:client:backchannel_logout_uri_help}'|trans }}
- + {{ form['post_logout_redirect_uri'].control | raw }} - {% trans '{oidc:client:post_logout_redirect_uri_help}' %} + {{ '{oidc:client:post_logout_redirect_uri_help}'|trans }}
- + {{ form['allowed_origin'].control | raw }} - {% trans '{oidc:client:allowed_origin_help}' %} + {{ '{oidc:client:allowed_origin_help}'|trans }}
@@ -92,7 +92,7 @@ {% block action %}{% endblock %} - {% trans '{oidc:return}' %} + {{ '{oidc:return}'|trans }} @@ -162,7 +162,7 @@ rules: [ { type: 'empty', - prompt: '{% trans "{oidc:client:name_not_empty}" %}' + prompt: '{{ "{oidc:client:name_not_empty}"|trans }}' } ] }, @@ -171,11 +171,11 @@ rules: [ { type: 'empty', - prompt: '{% trans "{oidc:client:redirect_uri_not_empty}" %}' + prompt: '{{ "{oidc:client:redirect_uri_not_empty}"|trans }}' }, { type: 'redirectUri', - prompt: '{% trans "{oidc:client:redirect_uri_not_valid}" %}' + prompt: '{{ "{oidc:client:redirect_uri_not_valid}"|trans }}' } ] }, @@ -188,7 +188,7 @@ rules: [ { type: 'minCount[1]', - prompt: '{% trans "{oidc:client:scopes_not_empty}" %}' + prompt: '{{ "{oidc:client:scopes_not_empty}"|trans }}' } ] }, @@ -197,7 +197,7 @@ rules: [ { type: 'allowedOrigin', - prompt: '{% trans "{oidc:client:allowed_origin_not_valid}" %}' + prompt: '{{ "{oidc:client:allowed_origin_not_valid}"|trans }}' } ] }, @@ -206,7 +206,7 @@ rules: [ { type: 'postLogoutRedirectUri', - prompt: '{% trans "{oidc:client:post_logout_redirect_uri_not_valid}" %}' + prompt: '{{ "{oidc:client:post_logout_redirect_uri_not_valid}"|trans }}' } ] }, @@ -215,7 +215,7 @@ rules: [ { type: 'backChannelLogoutUri', - prompt: '{% trans "{oidc:client:backchannel_logout_uri_not_valid}" %}' + prompt: '{{ "{oidc:client:backchannel_logout_uri_not_valid}"|trans }}' } ] } diff --git a/templates/clients/delete.twig b/templates/clients/delete.twig index 331270ed..e0916615 100644 --- a/templates/clients/delete.twig +++ b/templates/clients/delete.twig @@ -4,7 +4,7 @@ {% block pre_breadcrump %} / - {% trans 'OpenID Connect Client Registry' %} + {{ 'OpenID Connect Client Registry'|trans }} {% endblock %} {% block content %} @@ -14,28 +14,28 @@
- {% trans '{oidc:client:delete}' %} + {{ '{oidc:client:delete}'|trans }}
-

{% trans '{oidc:client:confirm_delete}' %}

+

{{ '{oidc:client:confirm_delete}'|trans }}

- +
- +
- {% trans '{oidc:return}' %} + {{ '{oidc:return}'|trans }}
diff --git a/templates/clients/edit.twig b/templates/clients/edit.twig index d6b81600..c3dadb70 100644 --- a/templates/clients/edit.twig +++ b/templates/clients/edit.twig @@ -4,12 +4,12 @@ {% block pre_breadcrump %} / - {% trans 'OpenID Connect Client Registry' %} + {{ 'OpenID Connect Client Registry'|trans }} {% endblock %} {% block action %}
- {% trans '{oidc:save}' %} + {{ '{oidc:save}'|trans }}
{% endblock %} diff --git a/templates/clients/index.twig b/templates/clients/index.twig index dc0bc74b..810a44a5 100644 --- a/templates/clients/index.twig +++ b/templates/clients/index.twig @@ -13,7 +13,7 @@
- +
Reset
@@ -26,11 +26,11 @@ - {% trans '{oidc:client_list}' %} + {{ '{oidc:client_list}'|trans }} - {% trans '{oidc:add_client}' %} + {{ '{oidc:add_client}'|trans }} @@ -60,7 +60,7 @@
- {% trans '{oidc:no_clients}' %} + {{ '{oidc:no_clients}'|trans }}
diff --git a/templates/clients/new.twig b/templates/clients/new.twig index 11a66548..39e30279 100644 --- a/templates/clients/new.twig +++ b/templates/clients/new.twig @@ -4,12 +4,12 @@ {% block pre_breadcrump %} / - {% trans 'OpenID Connect Client Registry' %} + {{ 'OpenID Connect Client Registry'|trans }} {% endblock %} {% block action %}
- {% trans '{oidc:create}' %} + {{ '{oidc:create}'|trans }}
{% endblock %} diff --git a/templates/clients/show.twig b/templates/clients/show.twig index f541c89f..9b6cc2b7 100644 --- a/templates/clients/show.twig +++ b/templates/clients/show.twig @@ -4,7 +4,7 @@ {% block pre_breadcrump %} / - {% trans 'OpenID Connect Client Registry' %} + {{ 'OpenID Connect Client Registry'|trans }} {% endblock %} {% block content %} @@ -15,45 +15,45 @@ - + - + - + - + - + - + - + - + - + - + - + - +
{% trans '{oidc:client:name}' %}{{ '{oidc:client:name}'|trans }} {{ client.name }}
{% trans '{oidc:client:description}' %}{{ '{oidc:client:description}'|trans }} {{ client.description }}
{% trans '{oidc:client:state}' %}{{ '{oidc:client:state}'|trans }} - {% trans '{oidc:client:is_enabled}' %} + {{ '{oidc:client:is_enabled}'|trans }} - {% trans '{oidc:client:is_confidential}' %} + {{ '{oidc:client:is_confidential}'|trans }}
{% trans '{oidc:client:identifier}' %}{{ '{oidc:client:identifier}'|trans }} {{ client.identifier }} - +
{% trans '{oidc:client:secret}' %}{{ '{oidc:client:secret}'|trans }} {{ client.secret }} - +
{% trans '{oidc:client:auth_source}' %}{{ '{oidc:client:auth_source}'|trans }} {{ client.authSourceId }}
{% trans '{oidc:client:redirect_uri}' %}{{ '{oidc:client:redirect_uri}'|trans }}
    {% for uri in client.redirectUri %} @@ -63,7 +63,7 @@
{% trans '{oidc:client:scopes}' %}{{ '{oidc:client:scopes}'|trans }}
    {% for key, scope in client.scopes %} @@ -73,11 +73,11 @@
{% trans '{oidc:client:backchannel_logout_uri}' %}{{ '{oidc:client:backchannel_logout_uri}'|trans }} {{ client.backChannelLogoutUri }}
{% trans '{oidc:client:post_logout_redirect_uri}' %}{{ '{oidc:client:post_logout_redirect_uri}'|trans }}
    {% for uri in client.postLogoutRedirectUri %} @@ -87,12 +87,12 @@
{% trans '{oidc:client:owner}' %}{{ '{oidc:client:owner}'|trans }} {{ client.owner }}
{% trans '{oidc:client:allowed_origin}' %}{{ '{oidc:client:allowed_origin}'|trans }}
    {% for allowedOrigin in allowedOrigins %} @@ -109,20 +109,20 @@
- {% trans '{oidc:return}' %} + {{ '{oidc:return}'|trans }} - {% trans '{oidc:edit}' %} + {{ '{oidc:edit}'|trans }}
- {% trans '{oidc:client:reset_secret}' %} + {{ '{oidc:client:reset_secret}'|trans }}
@@ -142,7 +142,7 @@ new ClipboardJS('.copy.link') .on('success', function (e) { e.clearSelection(); - $(e.trigger).popup('change content', '{% trans '{oidc:copied}' %}') + $(e.trigger).popup('change content', '{{ '{oidc:copied}'|trans }}') }) ; diff --git a/templates/install.twig b/templates/install.twig index 84d1371d..ea272a5c 100644 --- a/templates/install.twig +++ b/templates/install.twig @@ -9,24 +9,24 @@
- {% trans '{oidc:install}' %} + {{ '{oidc:install}'|trans }}
-

{% trans '{oidc:install:description}' %}

+

{{ '{oidc:install:description}'|trans }}

{% if oauth2_enabled %}
- +
{% endif %}
- {% trans '{oidc:return}' %} + {{ '{oidc:return}'|trans }}
diff --git a/templates/logout.twig b/templates/logout.twig index 423e7e66..024f634a 100644 --- a/templates/logout.twig +++ b/templates/logout.twig @@ -5,9 +5,9 @@ {% block content %}

{% if wasLogoutActionCalled %} - {% trans '{oidc:logout:page_title_success}' %} + {{ '{oidc:logout:page_title_success}'|trans }} {% else %} - {% trans '{oidc:logout:page_title_fail}' %} + {{ '{oidc:logout:page_title_fail}'|trans }} {% endif %}

@@ -15,13 +15,13 @@
- {% trans '{oidc:logout:info_title}' %} + {{ '{oidc:logout:info_title}'|trans }}

{% if wasLogoutActionCalled %} - {% trans '{oidc:logout:info_message_success}' %} + {{ '{oidc:logout:info_message_success}'|trans }} {% else %} - {% trans '{oidc:logout:info_message_fail}' %} + {{ '{oidc:logout:info_message_fail}'|trans }} {% endif %}

diff --git a/templates/oidc_base.twig b/templates/oidc_base.twig index c20b6b02..424c10b9 100644 --- a/templates/oidc_base.twig +++ b/templates/oidc_base.twig @@ -85,7 +85,7 @@ {% if messages is not empty %}
{% for message in messages %} -

{% trans message %}

+

{{ message|trans }}

{% endfor %}
{% endif %} diff --git a/tests/ClaimTranslatorExtractorTest.php b/tests/ClaimTranslatorExtractorTest.php index 6a1b6395..2dc71fc6 100644 --- a/tests/ClaimTranslatorExtractorTest.php +++ b/tests/ClaimTranslatorExtractorTest.php @@ -72,7 +72,7 @@ public function testTypeConversion(): void ], 'urn:oid:2.5.4.3' => ['stringAttribute'] ]; - $userAttributes = Attributes::normalizeAttributesArray( + $userAttributes = (new Attributes())->normalizeAttributesArray( [ 'intAttribute' => '7890', 'boolAttribute1' => '1', @@ -113,7 +113,7 @@ public function testTypeConversion(): void public function testDefaultTypeConversion(): void { // Address is the only non-string attribute with a default saml source - $userAttributes = Attributes::normalizeAttributesArray( + $userAttributes = (new Attributes())->normalizeAttributesArray( [ 'postalAddress' => 'myAddress' ] @@ -159,7 +159,7 @@ public function testStandardClaimTypesCanBeSet(): void ] ], ]; - $userAttributes = Attributes::normalizeAttributesArray( + $userAttributes = (new Attributes())->normalizeAttributesArray( [ 'country' => 'CA', 'postal' => '93105', @@ -200,7 +200,7 @@ public function testInvalidTypeConversion(): void 'testClaim' ], ]; - $userAttributes = Attributes::normalizeAttributesArray(['testClaim' => '7890F',]); + $userAttributes = (new Attributes())->normalizeAttributesArray(['testClaim' => '7890F',]); $claimTranslator = new ClaimTranslatorExtractor(self::$userIdAttr, [$claimSet], $translate); $claimTranslator->extract(['typeConversion'], $userAttributes); } diff --git a/tests/Controller/ClientCreateControllerTest.php b/tests/Controller/ClientCreateControllerTest.php index 4eac54eb..e18d0fe6 100644 --- a/tests/Controller/ClientCreateControllerTest.php +++ b/tests/Controller/ClientCreateControllerTest.php @@ -2,13 +2,226 @@ namespace SimpleSAML\Test\Module\oidc\Controller; +use Laminas\Diactoros\Response\RedirectResponse; +use Laminas\Diactoros\ServerRequest; use SimpleSAML\Module\oidc\Controller\ClientCreateController; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Entity\ClientEntity; +use SimpleSAML\Module\oidc\Factories\FormFactory; +use SimpleSAML\Module\oidc\Factories\TemplateFactory; +use SimpleSAML\Module\oidc\Form\ClientForm; +use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; +use SimpleSAML\Module\oidc\Repositories\ClientRepository; +use SimpleSAML\Module\oidc\Services\AuthContextService; +use SimpleSAML\Module\oidc\Services\SessionMessagesService; +use SimpleSAML\XHTML\Template; +/** + * @covers \SimpleSAML\Module\oidc\Controller\ClientCreateController + */ class ClientCreateControllerTest extends TestCase { - public function testIncomplete(): void + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $clientRepositoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $allowedOriginRepositoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $templateFactoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $formFactoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $sessionMessageServiceMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $authContextServiceMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $clientFormMock; + /** + * @var \PHPUnit\Framework\MockObject\Stub + */ + protected $serverRequestStub; + /** + * @var \PHPUnit\Framework\MockObject\Stub + */ + protected $templateStub; + + protected function setUp(): void + { + $this->clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); + $this->templateFactoryMock = $this->createMock(TemplateFactory::class); + $this->formFactoryMock = $this->createMock(FormFactory::class); + $this->sessionMessageServiceMock = $this->createMock(SessionMessagesService::class); + $this->authContextServiceMock = $this->createMock(AuthContextService::class); + + $this->clientFormMock = $this->createMock(ClientForm::class); + $this->serverRequestStub = $this->createStub(ServerRequest::class); + $this->templateStub = $this->createStub(Template::class); + } + + public function testCanInstantiate(): void + { + $controller = $this->getStubbedInstance(); + $this->assertInstanceOf(ClientCreateController::class, $controller); + } + + protected function getStubbedInstance(): ClientCreateController { - $this->markTestIncomplete(); + return new ClientCreateController( + $this->clientRepositoryMock, + $this->allowedOriginRepositoryMock, + $this->templateFactoryMock, + $this->formFactoryMock, + $this->sessionMessageServiceMock, + $this->authContextServiceMock + ); + } + + public function testCanShowNewClientForm(): void + { + $this->clientFormMock + ->expects($this->once()) + ->method('setAction') + ->with($this->anything()); + $this->clientFormMock + ->expects($this->once()) + ->method('isSuccess') + ->willReturn(false); + + $this->templateFactoryMock + ->expects($this->once()) + ->method('render') + ->with('oidc:clients/new.twig', [ + 'form' => $this->clientFormMock, + 'regexUri' => ClientForm::REGEX_URI, + 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, + 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, + ]) + ->willReturn($this->templateStub); + + $this->formFactoryMock + ->expects($this->once()) + ->method('build') + ->with($this->equalTo(ClientForm::class)) + ->willReturn($this->clientFormMock); + + $controller = $this->getStubbedInstance(); + $this->assertSame($this->templateStub, $controller->__invoke($this->serverRequestStub)); + } + + public function testCanCreateNewClientFromFormData(): void + { + $this->clientFormMock + ->expects($this->once()) + ->method('setAction') + ->with($this->anything()); + $this->clientFormMock + ->expects($this->once()) + ->method('isSuccess') + ->willReturn(true); + $this->clientFormMock + ->expects($this->once()) + ->method('getValues') + ->willReturn( + [ + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'is_confidential' => false, + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ] + ); + + $this->formFactoryMock + ->expects($this->once()) + ->method('build') + ->willReturn($this->clientFormMock); + + $this->clientRepositoryMock + ->expects($this->once()) + ->method('add') + ->with($this->isInstanceOf(ClientEntity::class)); + + $this->allowedOriginRepositoryMock + ->expects($this->once()) + ->method('set') + ->with($this->isType('string'), []); + $this->sessionMessageServiceMock + ->expects($this->once()) + ->method('addMessage') + ->with('{oidc:client:added}'); + + $controller = $this->getStubbedInstance(); + $this->assertInstanceOf(RedirectResponse::class, $controller->__invoke($this->serverRequestStub)); + } + + public function testCanSetOwnerInNewClient(): void + { + $this->authContextServiceMock->expects($this->once())->method('isSspAdmin')->willReturn(false); + $this->authContextServiceMock->method('getAuthUserId')->willReturn('ownerUsername'); + + $this->clientFormMock + ->expects($this->once()) + ->method('setAction') + ->with($this->anything()); + $this->clientFormMock + ->expects($this->once()) + ->method('isSuccess') + ->willReturn(true); + $this->clientFormMock + ->expects($this->once()) + ->method('getValues') + ->willReturn( + [ + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'is_confidential' => false, + 'owner' => 'wrongOwner', + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ] + ); + + $this->formFactoryMock + ->expects($this->once()) + ->method('build') + ->willReturn($this->clientFormMock); + + $this->clientRepositoryMock->expects($this->once())->method('add') + ->with($this->callback(function ($client) { + return is_callable([$client, 'getOwner']) && + $client->getOwner() == 'ownerUsername'; + })); + + $this->sessionMessageServiceMock + ->expects($this->once()) + ->method('addMessage') + ->with('{oidc:client:added}'); + + $controller = $this->getStubbedInstance(); + $this->assertInstanceOf(RedirectResponse::class, $controller->__invoke($this->serverRequestStub)); } } diff --git a/tests/Controller/ClientDeleteControllerTest.php b/tests/Controller/ClientDeleteControllerTest.php new file mode 100644 index 00000000..28d04465 --- /dev/null +++ b/tests/Controller/ClientDeleteControllerTest.php @@ -0,0 +1,184 @@ +clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->templateFactoryMock = $this->createMock(TemplateFactory::class); + $this->sessionMessageServiceMock = $this->createMock(SessionMessagesService::class); + $this->serverRequestMock = $this->createMock(ServerRequest::class); + $this->uriStub = $this->createStub(UriInterface::class); + $this->authContextServiceMock = $this->createMock(AuthContextService::class); + + $this->clientEntityMock = $this->createMock(ClientEntity::class); + $this->templateStub = $this->createStub(Template::class); + } + + protected function getStubbedInstance(): ClientDeleteController + { + return new ClientDeleteController( + $this->clientRepositoryMock, + $this->templateFactoryMock, + $this->sessionMessageServiceMock, + $this->authContextServiceMock + ); + } + + public function testCanInstantiate(): void + { + $controller = $this->getStubbedInstance(); + $this->assertInstanceOf(ClientDeleteController::class, $controller); + } + + public function testItAsksConfirmationBeforeDeletingClient(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->serverRequestMock->expects($this->once())->method('getParsedBody')->willReturn([]); + $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('get'); + $this->clientRepositoryMock->expects($this->once())->method('findById')->with('clientid') + ->willReturn($this->clientEntityMock); + $this->templateFactoryMock->expects($this->once())->method('render') + ->with('oidc:clients/delete.twig', ['client' => $this->clientEntityMock]) + ->willReturn($this->templateStub); + + $controller = $this->getStubbedInstance(); + + $this->assertInstanceOf(Template::class, $controller->__invoke($this->serverRequestMock)); + } + + public function testThrowsIfIdNotFoundInDeleteAction(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); + + $this->expectException(BadRequest::class); + + ($this->getStubbedInstance())->__invoke($this->serverRequestMock); + } + + public function testThrowsIfSecretNotFoundInDeleteAction(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->serverRequestMock->expects($this->once())->method('getParsedBody')->willReturn([]); + $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->clientEntityMock); + + $this->expectException(BadRequest::class); + + ($this->getStubbedInstance())->__invoke($this->serverRequestMock); + } + + public function testThrowsIfSecretIsInvalidInDeleteAction(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->serverRequestMock->expects($this->once())->method('getParsedBody') + ->willReturn(['secret' => 'invalidsecret']); + $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->clientEntityMock); + + $this->expectException(BadRequest::class); + + ($this->getStubbedInstance())->__invoke($this->serverRequestMock); + } + + public function testItDeletesClient(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->serverRequestMock->expects($this->once())->method('getParsedBody') + ->willReturn(['secret' => 'validsecret']); + $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->clientEntityMock); + $this->clientRepositoryMock->expects($this->once())->method('delete') + ->with($this->clientEntityMock, null); + $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') + ->with('{oidc:client:removed}'); + + $this->assertInstanceOf( + RedirectResponse::class, + ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ); + } + + public function testItDeletesClientWithOwner(): void + { + $this->authContextServiceMock->expects($this->exactly(2))->method('isSspAdmin')->willReturn(false); + $this->authContextServiceMock->expects($this->exactly(2))->method('getAuthUserId')->willReturn('theOwner'); + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->serverRequestMock->expects($this->once())->method('getParsedBody') + ->willReturn(['secret' => 'validsecret']); + $this->serverRequestMock->expects($this->once())->method('getMethod')->willReturn('post'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->clientEntityMock); + $this->clientRepositoryMock->expects($this->once())->method('delete') + ->with($this->clientEntityMock, 'theOwner'); + $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') + ->with('{oidc:client:removed}'); + + $this->assertInstanceOf( + RedirectResponse::class, + ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ); + } +} diff --git a/tests/Controller/ClientEditControllerTest.php b/tests/Controller/ClientEditControllerTest.php index 65e4377f..8848dd58 100644 --- a/tests/Controller/ClientEditControllerTest.php +++ b/tests/Controller/ClientEditControllerTest.php @@ -2,13 +2,348 @@ namespace SimpleSAML\Test\Module\oidc\Controller; +use Laminas\Diactoros\Response\RedirectResponse; +use Laminas\Diactoros\ServerRequest; +use Psr\Http\Message\UriInterface; +use SimpleSAML\Error\BadRequest; +use SimpleSAML\Error\NotFound; use SimpleSAML\Module\oidc\Controller\ClientEditController; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\oidc\Entity\ClientEntity; +use SimpleSAML\Module\oidc\Factories\FormFactory; +use SimpleSAML\Module\oidc\Factories\TemplateFactory; +use SimpleSAML\Module\oidc\Form\ClientForm; +use SimpleSAML\Module\oidc\Repositories\AllowedOriginRepository; +use SimpleSAML\Module\oidc\Repositories\ClientRepository; +use SimpleSAML\Module\oidc\Services\AuthContextService; +use SimpleSAML\Module\oidc\Services\ConfigurationService; +use SimpleSAML\Module\oidc\Services\SessionMessagesService; +use SimpleSAML\XHTML\Template; +/** + * @covers \SimpleSAML\Module\oidc\Controller\ClientEditController + */ class ClientEditControllerTest extends TestCase { - public function testIncomplete(): void + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $configurationServiceMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $clientRepositoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $allowedOriginRepositoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $templateFactoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $formFactoryMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $sessionMessageServiceMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $serverRequestMock; + /** + * @var \PHPUnit\Framework\MockObject\Stub + */ + protected $uriStub; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $authContextServiceMock; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $clientEntityMock; + /** + * @var \PHPUnit\Framework\MockObject\Stub + */ + protected $templateStub; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + protected $clientFormMock; + + protected function setUp(): void + { + $this->configurationServiceMock = $this->createMock(ConfigurationService::class); + $this->clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->allowedOriginRepositoryMock = $this->createMock(AllowedOriginRepository::class); + $this->templateFactoryMock = $this->createMock(TemplateFactory::class); + $this->formFactoryMock = $this->createMock(FormFactory::class); + $this->sessionMessageServiceMock = $this->createMock(SessionMessagesService::class); + $this->authContextServiceMock = $this->createMock(AuthContextService::class); + $this->serverRequestMock = $this->createMock(ServerRequest::class); + $this->uriStub = $this->createStub(UriInterface::class); + + $this->clientEntityMock = $this->createMock(ClientEntity::class); + $this->templateStub = $this->createStub(Template::class); + $this->clientFormMock = $this->createMock(ClientForm::class); + + $this->configurationServiceMock->method('getOpenIdConnectModuleURL')->willReturn('url'); + $this->uriStub->method('getPath')->willReturn('/'); + $this->serverRequestMock->method('getUri')->willReturn($this->uriStub); + $this->serverRequestMock->method('withQueryParams')->willReturn($this->serverRequestMock); + } + + protected function getStubbedInstance(): ClientEditController + { + return new ClientEditController( + $this->configurationServiceMock, + $this->clientRepositoryMock, + $this->allowedOriginRepositoryMock, + $this->templateFactoryMock, + $this->formFactoryMock, + $this->sessionMessageServiceMock, + $this->authContextServiceMock + ); + } + + public function testItIsInitializable(): void + { + $this->assertInstanceOf(ClientEditController::class, $this->getStubbedInstance()); + } + + public function testItShowsEditClientForm(): void + { + $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); + + $data = [ + 'id' => 'clientid', + 'secret' => 'validsecret', + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ]; + + $this->clientEntityMock->expects($this->atLeastOnce())->method('getIdentifier')->willReturn('clientid'); + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->clientEntityMock->expects($this->once())->method('toArray')->willReturn($data); + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->clientEntityMock); + $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') + ->willReturn([]); + $this->clientFormMock->expects($this->once())->method('setAction'); + $this->clientFormMock->expects($this->once())->method('setDefaults')->with($data); + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(false); + $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); + $this->templateFactoryMock->expects($this->once())->method('render')->with( + 'oidc:clients/edit.twig', + [ + 'form' => $this->clientFormMock, + 'regexUri' => ClientForm::REGEX_URI, + 'regexAllowedOriginUrl' => ClientForm::REGEX_ALLOWED_ORIGIN_URL, + 'regexHttpUri' => ClientForm::REGEX_HTTP_URI, + ] + )->willReturn($this->templateStub); + + $this->assertSame( + ($this->getStubbedInstance())->__invoke($this->serverRequestMock), + $this->templateStub + ); + } + + public function testItUpdatesClientFromEditClientFormData(): void + { + $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); + + $data = [ + 'id' => 'clientid', + 'secret' => 'validsecret', + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'is_confidential' => false, + 'owner' => 'existingOwner', + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ]; + + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + + $this->clientEntityMock->expects($this->atLeastOnce())->method('getIdentifier')->willReturn('clientid'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); + $this->clientEntityMock->expects($this->once())->method('getOwner')->willReturn('existingOwner'); + $this->clientEntityMock->expects($this->once())->method('toArray')->willReturn($data); + + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->willReturn($this->clientEntityMock); + + $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') + ->willReturn([]); + + $this->clientFormMock->expects($this->once())->method('setAction'); + $this->clientFormMock->expects($this->once())->method('setDefaults')->with($data); + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->expects($this->once())->method('getValues')->willReturn( + [ + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'is_confidential' => false, + 'owner' => 'existingOwner', + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ] + ); + + $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); + + $this->clientRepositoryMock->expects($this->once())->method('update')->with( + ClientEntity::fromData( + 'clientid', + 'validsecret', + 'name', + 'description', + ['http://localhost/redirect'], + ['openid'], + true, + false, + 'auth_source', + 'existingOwner', + [] + ), + null + ); + + $this->allowedOriginRepositoryMock->expects($this->once())->method('set')->with('clientid', []); + $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') + ->with('{oidc:client:updated}'); + + $this->assertInstanceOf( + RedirectResponse::class, + ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ); + } + + public function testItSendsOwnerArgToRepoOnUpdate(): void { - $this->markTestIncomplete(); + $this->authContextServiceMock->expects($this->atLeastOnce())->method('isSspAdmin')->willReturn(false); + $this->authContextServiceMock->method('getAuthUserId')->willReturn('authedUserId'); + $data = [ + 'id' => 'clientid', + 'secret' => 'validsecret', + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'is_confidential' => false, + 'owner' => 'existingOwner', + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ]; + + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + + $this->clientEntityMock->expects($this->atLeastOnce())->method('getIdentifier')->willReturn('clientid'); + $this->clientEntityMock->expects($this->once())->method('getSecret')->willReturn('validsecret'); + $this->clientEntityMock->expects($this->once())->method('getOwner')->willReturn('existingOwner'); + $this->clientEntityMock->expects($this->once())->method('toArray')->willReturn($data); + + $this->clientRepositoryMock->expects($this->once())->method('findById') + ->with('clientid', 'authedUserId')->willReturn($this->clientEntityMock); + + $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') + ->willReturn([]); + + $this->clientFormMock->expects($this->once())->method('setAction'); + $this->clientFormMock->expects($this->once())->method('setDefaults')->with($data); + $this->clientFormMock->expects($this->once())->method('isSuccess')->willReturn(true); + $this->clientFormMock->expects($this->once())->method('getValues')->willReturn( + [ + 'name' => 'name', + 'description' => 'description', + 'auth_source' => 'auth_source', + 'redirect_uri' => ['http://localhost/redirect'], + 'scopes' => ['openid'], + 'is_enabled' => true, + 'is_confidential' => false, + 'owner' => 'existingOwner', + 'allowed_origin' => [], + 'post_logout_redirect_uri' => [], + 'backchannel_logout_uri' => null, + ] + ); + + $this->formFactoryMock->expects($this->once())->method('build')->willReturn($this->clientFormMock); + + $this->clientRepositoryMock->expects($this->once())->method('update')->with( + ClientEntity::fromData( + 'clientid', + 'validsecret', + 'name', + 'description', + ['http://localhost/redirect'], + ['openid'], + true, + false, + 'auth_source', + 'existingOwner', + [], + null + ), + 'authedUserId' + ); + + $this->allowedOriginRepositoryMock->expects($this->once())->method('get')->with('clientid') + ->willReturn([]); + $this->allowedOriginRepositoryMock->expects($this->once())->method('set')->with('clientid', []); + $this->sessionMessageServiceMock->expects($this->once())->method('addMessage') + ->with('{oidc:client:updated}'); + + $this->assertInstanceOf( + RedirectResponse::class, + ($this->getStubbedInstance())->__invoke($this->serverRequestMock) + ); + } + + public function testThrowsIdNotFoundExceptionInEditAction(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams')->willReturn([]); + + $this->expectException(BadRequest::class); + + ($this->getStubbedInstance())->__invoke($this->serverRequestMock); + } + + public function testThrowsClientNotFoundExceptionInEditAction(): void + { + $this->serverRequestMock->expects($this->once())->method('getQueryParams') + ->willReturn(['client_id' => 'clientid']); + $this->clientRepositoryMock->expects($this->once())->method('findById')->willReturn(null); + + $this->expectException(\Exception::class); + + ($this->getStubbedInstance())->__invoke($this->serverRequestMock); } } diff --git a/tests/Controller/ClientIndexControllerTest.php b/tests/Controller/ClientIndexControllerTest.php new file mode 100644 index 00000000..3385e907 --- /dev/null +++ b/tests/Controller/ClientIndexControllerTest.php @@ -0,0 +1,104 @@ +clientRepositoryMock = $this->createMock(ClientRepository::class); + $this->templateFactoryMock = $this->createMock(TemplateFactory::class); + $this->authContextServiceMock = $this->createMock(AuthContextService::class); + $this->serverRequestMock = $this->createMock(ServerRequest::class); + $this->uriStub = $this->createStub(UriInterface::class); + + $this->templateStub = $this->createStub(Template::class); + + $this->authContextServiceMock->method('isSspAdmin')->willReturn(true); + $this->uriStub->method('getPath')->willReturn('/'); + $this->serverRequestMock->method('getUri')->willReturn($this->uriStub); + $this->serverRequestMock->method('getQueryParams')->willReturn(['page' => 1]); + } + + protected function getStubbedInstance(): ClientIndexController + { + return new ClientIndexController( + $this->clientRepositoryMock, + $this->templateFactoryMock, + $this->authContextServiceMock + ); + } + + public function testItIsInitializable(): void + { + $this->assertInstanceOf(ClientIndexController::class, $this->getStubbedInstance()); + } + + public function testItShowsClientIndex(): void + { + $this->clientRepositoryMock->expects($this->once())->method('findPaginated') + ->with(1, '', null) + ->willReturn( + [ + 'items' => [], + 'numPages' => 1, + 'currentPage' => 1 + ] + ); + + $this->templateFactoryMock->expects($this->once())->method('render')->with( + 'oidc:clients/index.twig', + [ + 'clients' => [], + 'numPages' => 1, + 'currentPage' => 1, + 'query' => '', + ] + )->willReturn($this->templateStub); + + $this->assertSame($this->templateStub, ($this->getStubbedInstance())->__invoke($this->serverRequestMock)); + } +} diff --git a/tests/Repositories/AccessTokenRepositoryTest.php b/tests/Repositories/AccessTokenRepositoryTest.php index 59a373b3..bfcb19b5 100644 --- a/tests/Repositories/AccessTokenRepositoryTest.php +++ b/tests/Repositories/AccessTokenRepositoryTest.php @@ -44,7 +44,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Repositories/AllowedOriginRepositoryTest.php b/tests/Repositories/AllowedOriginRepositoryTest.php index 53413032..00a17019 100644 --- a/tests/Repositories/AllowedOriginRepositoryTest.php +++ b/tests/Repositories/AllowedOriginRepositoryTest.php @@ -39,7 +39,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Repositories/AuthCodeRepositoryTest.php b/tests/Repositories/AuthCodeRepositoryTest.php index 5662f74e..a25d6ef3 100644 --- a/tests/Repositories/AuthCodeRepositoryTest.php +++ b/tests/Repositories/AuthCodeRepositoryTest.php @@ -45,7 +45,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Repositories/ClientRepositoryTest.php b/tests/Repositories/ClientRepositoryTest.php index a1a14bcd..d52be588 100644 --- a/tests/Repositories/ClientRepositoryTest.php +++ b/tests/Repositories/ClientRepositoryTest.php @@ -38,7 +38,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Repositories/RefreshTokenRepositoryTest.php b/tests/Repositories/RefreshTokenRepositoryTest.php index 1faea6f9..be1fcf0e 100644 --- a/tests/Repositories/RefreshTokenRepositoryTest.php +++ b/tests/Repositories/RefreshTokenRepositoryTest.php @@ -46,7 +46,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Repositories/ScopeRepositoryTest.php b/tests/Repositories/ScopeRepositoryTest.php index 235ef85d..867154f4 100644 --- a/tests/Repositories/ScopeRepositoryTest.php +++ b/tests/Repositories/ScopeRepositoryTest.php @@ -32,7 +32,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Repositories/UserRepositoryTest.php b/tests/Repositories/UserRepositoryTest.php index 893775a2..bce4fd09 100644 --- a/tests/Repositories/UserRepositoryTest.php +++ b/tests/Repositories/UserRepositoryTest.php @@ -36,7 +36,7 @@ protected function setUp(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Server/Grants/AuthCodeGrantTest.php b/tests/Server/Grants/AuthCodeGrantTest.php index a1027bb4..a309c619 100644 --- a/tests/Server/Grants/AuthCodeGrantTest.php +++ b/tests/Server/Grants/AuthCodeGrantTest.php @@ -16,24 +16,24 @@ class AuthCodeGrantTest extends TestCase { /** - * @var \PHPUnit\Framework\MockObject\Stub|AuthCodeRepositoryInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $authCodeRepositoryStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|AccessTokenRepositoryInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $accessTokenRepositoryStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|RefreshTokenRepositoryInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $refreshTokenRepositoryStub; protected \DateInterval $authCodeTtl; /** - * @var \PHPUnit\Framework\MockObject\Stub|RequestRulesManager + * @var \PHPUnit\Framework\MockObject\Stub */ protected $requestRulesManagerStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|ConfigurationService + * @var \PHPUnit\Framework\MockObject\Stub */ protected $configurationServiceStub; diff --git a/tests/Server/Validators/BearerTokenValidatorTest.php b/tests/Server/Validators/BearerTokenValidatorTest.php index f47d6029..66a28d07 100644 --- a/tests/Server/Validators/BearerTokenValidatorTest.php +++ b/tests/Server/Validators/BearerTokenValidatorTest.php @@ -111,7 +111,7 @@ public static function setUpBeforeClass(): void Configuration::loadFromArray($config, '', 'simplesaml'); self::$publicKeyPath = $tempDir . '/oidc_module.crt'; - self::$privateKeyPath = $tempDir . '/oidc_module.pem'; + self::$privateKeyPath = $tempDir . '/oidc_module.key'; $pkGenerate = openssl_pkey_new([ 'private_key_bits' => 2048, diff --git a/tests/Services/ConfigurationServiceTest.php b/tests/Services/ConfigurationServiceTest.php index 8fff8e6e..cdbce5af 100644 --- a/tests/Services/ConfigurationServiceTest.php +++ b/tests/Services/ConfigurationServiceTest.php @@ -26,20 +26,20 @@ public function testSigningKeyNameCanBeCustomized(): void // Test default cert and pem $service = new ConfigurationService(); $this->assertEquals($certDir . 'oidc_module.crt', $service->getCertPath()); - $this->assertEquals($certDir . 'oidc_module.pem', $service->getPrivateKeyPath()); + $this->assertEquals($certDir . 'oidc_module.key', $service->getPrivateKeyPath()); // Set customized Configuration::setPreLoadedConfig( Configuration::loadFromArray( [ 'privatekey' => 'myPrivateKey.key', - 'certificate' => 'myCertificate.pem', + 'certificate' => 'myCertificate.crt', ] ), 'module_oidc.php' ); $service = new ConfigurationService(); - $this->assertEquals($certDir . 'myCertificate.pem', $service->getCertPath()); + $this->assertEquals($certDir . 'myCertificate.crt', $service->getCertPath()); $this->assertEquals($certDir . 'myPrivateKey.key', $service->getPrivateKeyPath()); } } diff --git a/tests/Services/JsonWebKeySetServiceTest.php b/tests/Services/JsonWebKeySetServiceTest.php index d3ca5721..20037bc9 100644 --- a/tests/Services/JsonWebKeySetServiceTest.php +++ b/tests/Services/JsonWebKeySetServiceTest.php @@ -40,16 +40,10 @@ public static function setUpBeforeClass(): void 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); - // get the private key - openssl_pkey_export($pkGenerate, $pkGeneratePrivate); - // get the public key $pkGenerateDetails = openssl_pkey_get_details($pkGenerate); self::$pkGeneratePublic = $pkGenerateDetails['key']; - // free resources - openssl_pkey_free($pkGenerate); - file_put_contents(sys_get_temp_dir() . '/oidc_module.crt', self::$pkGeneratePublic); Configuration::setPreLoadedConfig( diff --git a/tests/Services/JsonWebTokenBuilderServiceTest.php b/tests/Services/JsonWebTokenBuilderServiceTest.php index 6a92d5fe..b1c940e1 100644 --- a/tests/Services/JsonWebTokenBuilderServiceTest.php +++ b/tests/Services/JsonWebTokenBuilderServiceTest.php @@ -34,7 +34,7 @@ class JsonWebTokenBuilderServiceTest extends TestCase public static function setUpBeforeClass(): void { self::$certFolder = dirname(__DIR__, 2) . '/docker/ssp/'; - self::$privateKeyPath = self::$certFolder . 'oidc_module.pem'; + self::$privateKeyPath = self::$certFolder . 'oidc_module.key'; self::$publicKeyPath = self::$certFolder . 'oidc_module.crt'; self::$signerSha256 = new Sha256(); } diff --git a/tests/Services/LogoutTokenBuilderTest.php b/tests/Services/LogoutTokenBuilderTest.php index a838edf2..4b6f1cd7 100644 --- a/tests/Services/LogoutTokenBuilderTest.php +++ b/tests/Services/LogoutTokenBuilderTest.php @@ -46,7 +46,7 @@ class LogoutTokenBuilderTest extends TestCase public static function setUpBeforeClass(): void { self::$certFolder = dirname(__DIR__, 2) . '/docker/ssp/'; - self::$privateKeyPath = self::$certFolder . 'oidc_module.pem'; + self::$privateKeyPath = self::$certFolder . 'oidc_module.key'; self::$publicKeyPath = self::$certFolder . 'oidc_module.crt'; self::$signerSha256 = new Sha256(); } diff --git a/tests/Store/SessionLogoutTicketStoreBuilderTest.php b/tests/Store/SessionLogoutTicketStoreBuilderTest.php index ae1eda72..0eb62732 100644 --- a/tests/Store/SessionLogoutTicketStoreBuilderTest.php +++ b/tests/Store/SessionLogoutTicketStoreBuilderTest.php @@ -20,7 +20,7 @@ public function testConstructWithDefaultStore(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Store/SessionLogoutTicketStoreDbTest.php b/tests/Store/SessionLogoutTicketStoreDbTest.php index b2edb2d5..fb113c8c 100644 --- a/tests/Store/SessionLogoutTicketStoreDbTest.php +++ b/tests/Store/SessionLogoutTicketStoreDbTest.php @@ -21,7 +21,7 @@ public static function setUpBeforeClass(): void 'database.password' => null, 'database.prefix' => 'phpunit_', 'database.persistent' => true, - 'database.slaves' => [], + 'database.secondaries' => [], ]; Configuration::loadFromArray($config, '', 'simplesaml'); diff --git a/tests/Utils/Checker/Rules/IdTokenHintRuleTest.php b/tests/Utils/Checker/Rules/IdTokenHintRuleTest.php index ba50156d..a5b8ada7 100644 --- a/tests/Utils/Checker/Rules/IdTokenHintRuleTest.php +++ b/tests/Utils/Checker/Rules/IdTokenHintRuleTest.php @@ -43,7 +43,7 @@ class IdTokenHintRuleTest extends TestCase public static function setUpBeforeClass(): void { self::$certFolder = dirname(__DIR__, 4) . '/docker/ssp/'; - self::$privateKeyPath = self::$certFolder . 'oidc_module.pem'; + self::$privateKeyPath = self::$certFolder . 'oidc_module.key'; self::$publicKeyPath = self::$certFolder . 'oidc_module.crt'; self::$privateKey = new CryptKey(self::$privateKeyPath, null, false); self::$publicKey = new CryptKey(self::$publicKeyPath, null, false); @@ -66,6 +66,7 @@ protected function setUp(): void $this->cryptKeyFactoryStub->method('buildPrivateKey')->willReturn(self::$privateKey); $this->cryptKeyFactoryStub->method('buildPublicKey')->willReturn(self::$publicKey); + /** @psalm-suppress ArgumentTypeCoercion */ $this->jwtConfig = Configuration::forAsymmetricSigner( $this->configurationServiceStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()), @@ -90,6 +91,7 @@ public function testConstruct(): void public function testCheckRuleIsNullWhenParamNotSet(): void { $rule = new IdTokenHintRule($this->configurationServiceStub, $this->cryptKeyFactoryStub); + $this->requestStub->method('getMethod')->willReturn(''); $result = $rule->checkRule( $this->requestStub, $this->resultBagStub, @@ -138,6 +140,7 @@ public function testCheckRuleThrowsForIdTokenWithInvalidIssuer(): void { $this->requestStub->method('getMethod')->willReturn('GET'); + /** @psalm-suppress ArgumentTypeCoercion */ $invalidIssuerJwt = $this->jwtConfig->builder()->issuedBy('invalid')->getToken( $this->configurationServiceStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()) @@ -158,6 +161,7 @@ public function testCheckRulePassesForValidIdToken(): void { $this->requestStub->method('getMethod')->willReturn('GET'); + /** @psalm-suppress ArgumentTypeCoercion */ $idToken = $this->jwtConfig->builder()->issuedBy(self::$issuer)->getToken( $this->configurationServiceStub->getSigner(), InMemory::plainText(self::$privateKey->getKeyContents()) diff --git a/tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php b/tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php index f3204185..d73557ac 100644 --- a/tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php +++ b/tests/Utils/Checker/Rules/PostLogoutRedirectUriRuleTest.php @@ -44,7 +44,7 @@ class PostLogoutRedirectUriRuleTest extends TestCase public static function setUpBeforeClass(): void { self::$certFolder = dirname(__DIR__, 4) . '/docker/ssp/'; - self::$privateKeyPath = self::$certFolder . 'oidc_module.pem'; + self::$privateKeyPath = self::$certFolder . 'oidc_module.key'; self::$publicKeyPath = self::$certFolder . 'oidc_module.crt'; self::$privateKey = new CryptKey(self::$privateKeyPath, null, false); self::$publicKey = new CryptKey(self::$publicKeyPath, null, false); @@ -58,6 +58,7 @@ protected function setUp(): void $this->resultBagStub = $this->createStub(ResultBagInterface::class); $this->clientStub = $this->createStub(ClientEntityInterface::class); + /** @psalm-suppress ArgumentTypeCoercion */ $this->jwtConfig = Configuration::forAsymmetricSigner( new Sha256(), InMemory::plainText(self::$privateKey->getKeyContents()), @@ -103,6 +104,7 @@ public function testCheckRuleThrowsWhenAudClaimNotValid(): void $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); + /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder()->issuedBy(self::$issuer) ->getToken( new Sha256(), @@ -130,6 +132,7 @@ public function testCheckRuleThrowsWhenClientNotFound(): void $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); + /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder() ->issuedBy(self::$issuer) ->permittedFor('invalid-client-id') @@ -160,6 +163,7 @@ public function testCheckRuleThrowsWhenPostLogoutRegisteredUriNotRegistered(): v $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); + /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder() ->issuedBy(self::$issuer) ->permittedFor('client-id') @@ -195,6 +199,7 @@ public function testCheckRuleReturnsForRegisteredPostLogoutRedirectUri(): void $this->requestStub->method('getQueryParams') ->willReturn(['post_logout_redirect_uri' => self::$postLogoutRedirectUri]); + /** @psalm-suppress ArgumentTypeCoercion */ $jwt = $this->jwtConfig->builder() ->issuedBy(self::$issuer) ->permittedFor('client-id') diff --git a/tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php b/tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php index 93587999..266c0539 100644 --- a/tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php +++ b/tests/Utils/Checker/Rules/ScopeOfflineAccessRuleTest.php @@ -21,51 +21,51 @@ class ScopeOfflineAccessRuleTest extends TestCase { /** - * @var \PHPUnit\Framework\MockObject\Stub|ServerRequestInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $serverRequestStub; /** - * @var \PHPUnit\Framework\MockObject\MockObject|ResultBagInterface + * @var \PHPUnit\Framework\MockObject\MockObject */ protected $resultBagMock; /** - * @var \PHPUnit\Framework\MockObject\MockObject|LoggerService + * @var \PHPUnit\Framework\MockObject\MockObject */ protected $loggerServiceMock; /** - * @var \PHPUnit\Framework\MockObject\Stub|ClientEntityInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $clientStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|ScopeEntityInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $scopeEntityOpenid; /** - * @var \PHPUnit\Framework\MockObject\Stub|ScopeEntityInterface + * @var \PHPUnit\Framework\MockObject\Stub */ protected $scopeEntityOfflineAccess; /** - * @var \PHPUnit\Framework\MockObject\Stub|Result + * @var \PHPUnit\Framework\MockObject\Stub */ protected $redirectUriResultStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|Result + * @var \PHPUnit\Framework\MockObject\Stub */ protected $stateResultStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|Result + * @var \PHPUnit\Framework\MockObject\Stub */ protected $clientResultStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|Result + * @var \PHPUnit\Framework\MockObject\Stub */ protected $validScopesResultStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|ConfigurationService + * @var \PHPUnit\Framework\MockObject\Stub */ protected $configurationServiceStub; /** - * @var \PHPUnit\Framework\MockObject\Stub|Configuration + * @var \PHPUnit\Framework\MockObject\Stub */ protected $openIdConfigurationStub; @@ -102,31 +102,6 @@ public function testCanCreateInstance(): void ); } - public function testReturnsTrueWhenDeployerSetToAlwaysIssueRefreshToken(): void - { - $this->clientStub->method('getScopes')->willReturn(['openid']); - $this->clientResultStub->method('getValue')->willReturn($this->clientStub); - $this->validScopesResultStub->method('getValue')->willReturn([$this->scopeEntityOpenid]); - - $this->resultBagMock - ->method('getOrFail') - ->willReturnOnConsecutiveCalls( - $this->redirectUriResultStub, - $this->stateResultStub, - $this->clientResultStub, - $this->validScopesResultStub - ); - - $this->openIdConfigurationStub->method('getBoolean')->willReturn(true); - $this->configurationServiceStub->method('getOpenIDConnectConfiguration') - ->willReturn($this->openIdConfigurationStub); - - $result = (new ScopeOfflineAccessRule($this->configurationServiceStub)) - ->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); - - $this->assertTrue($result->getValue()); - } - public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void { $this->clientStub->method('getScopes')->willReturn(['openid']); @@ -149,6 +124,7 @@ public function testReturnsFalseWhenOfflineAccessScopeNotPresent(): void $result = (new ScopeOfflineAccessRule($this->configurationServiceStub)) ->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $this->assertNotNull($result); $this->assertFalse($result->getValue()); } @@ -201,6 +177,7 @@ public function testReturnsTrueWhenClientDoesHaveOfflineAccessScopeRegistered(): $result = (new ScopeOfflineAccessRule($this->configurationServiceStub)) ->checkRule($this->serverRequestStub, $this->resultBagMock, $this->loggerServiceMock); + $this->assertNotNull($result); $this->assertTrue($result->getValue()); } }