diff --git a/.github/workflows/test-and-publish.yml b/.github/workflows/test-and-publish.yml index 05245c7b..74986898 100644 --- a/.github/workflows/test-and-publish.yml +++ b/.github/workflows/test-and-publish.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: test - run: docker-compose -f actions-services.yml run --rm app ./run-tests.sh + run: docker-compose -f actions-services.yml run --rm test ./run-tests.sh - name: check hub metadata for tests run: docker-compose -f actions-services.yml run --rm ssp-hub.local ./run-metadata-tests.sh - name: check idp metadata for tests diff --git a/.gitignore b/.gitignore index 9f34c408..af9e176a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ composer.phar *.aes dockercfg node_modules/ +features/screenshots/ diff --git a/Dockerfile b/Dockerfile index a68fb93e..ec614245 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,16 +34,22 @@ RUN curl https://raw.githubusercontent.com/silinternational/s3-expand/1.5/s3-exp WORKDIR /data # Install/cleanup composer dependencies +ARG COMPOSER_FLAGS="--prefer-dist --no-interaction --no-dev --optimize-autoloader --no-scripts --no-progress" COPY composer.json /data/ COPY composer.lock /data/ RUN composer self-update --no-interaction -RUN COMPOSER_ALLOW_SUPERUSER=1 composer install --prefer-dist --no-interaction --no-dev --optimize-autoloader --no-scripts --no-progress +RUN COMPOSER_ALLOW_SUPERUSER=1 composer install $COMPOSER_FLAGS ENV SSP_PATH /data/vendor/simplesamlphp/simplesamlphp # Copy modules into simplesamlphp COPY modules/ $SSP_PATH/modules +# Copy material theme templates to other modules, just in case the "default" theme is selected +COPY modules/material/themes/material/expirychecker/* $SSP_PATH/modules/expirychecker/templates/ +COPY modules/material/themes/material/mfa/* $SSP_PATH/modules/mfa/templates/ +COPY modules/material/themes/material/profilereview/* $SSP_PATH/modules/profilereview/templates/ + # Copy in SSP override files RUN mv $SSP_PATH/www/index.php $SSP_PATH/www/ssp-index.php COPY dockerbuild/ssp-overrides/index.php $SSP_PATH/www/index.php diff --git a/Makefile b/Makefile index 6fd4442f..92e85ba8 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,8 @@ test-integration: docker-compose run --rm test ./run-integration-tests.sh copyJsLib: - cp ./node_modules/@simplewebauthn/browser/dist/bundle/index.umd.min.js ./modules/material/www/simplewebauthn/browser.js - cp ./node_modules/@simplewebauthn/browser/LICENSE.md ./www/simplewebauthn/LICENSE.md + cp ./node_modules/@simplewebauthn/browser/dist/bundle/index.umd.min.js ./modules/mfa/www/simplewebauthn/browser.js + cp ./node_modules/@simplewebauthn/browser/LICENSE.md ./modules/mfa/www/simplewebauthn/LICENSE.md deps: docker-compose run --rm node npm install --ignore-scripts diff --git a/README.md b/README.md index 3f908f36..00cac4a4 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,26 @@ will overwrite variables set in the execution environment. 4. `make` or `docker-compose up -d` within the project root. 5. Visit http://ssp-hub.local to see SimpleSAMLphp +### Configure a container for debugging with Xdebug + +1. Add a volume map for run-debug.sh on the container you wish to debug. + +```yml + - ./development/run-debug.sh:/data/run-debug.sh +``` + +2. Add or change the `command` for the container. + +```yml + command: /data/run-debug.sh +``` + +3. Restart the container. + +```shell +docker composer up -d ssp-hub.local +``` + ### Setup PhpStorm for remote debugging with Docker 1. Make sure you're running PhpStorm 2016.3 or later @@ -192,7 +212,7 @@ Update `/simplesamlphp/config/config.php`: 'theme.use' => 'material:material' ``` -This project provides a convenience by loading this config with whatever is in the environment variable `THEME_USE`._ +This project sets this as the default value in the provided config file. ##### Google reCAPTCHA diff --git a/actions-services.yml b/actions-services.yml index abcd117f..b67396b1 100644 --- a/actions-services.yml +++ b/actions-services.yml @@ -10,8 +10,11 @@ services: MYSQL_USER: silauth MYSQL_PASSWORD: silauth - app: - build: . + test: + build: + context: . + args: + COMPOSER_FLAGS: "--no-interaction --no-progress" depends_on: - ssp-hub.local - ssp-idp1.local @@ -28,7 +31,6 @@ services: MYSQL_USER: silauth MYSQL_PASSWORD: silauth PROFILE_URL_FOR_TESTS: http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: b SECRET_SALT: abc123 IDP_NAME: x @@ -53,6 +55,7 @@ services: # Utilize custom configs - ./development/hub/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + - ./development/announcement.php:/data/vendor/simplesamlphp/simplesamlphp/announcement/announcement.php # Utilize custom metadata - ./development/hub/metadata/idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/idp-remote.php @@ -63,14 +66,12 @@ services: # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "abc123" SECRET_SALT: "not-secret-h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "Hub" SECURE_COOKIE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "material:material" THEME_COLOR_SCHEME: "orange-light_blue" HUB_MODE: "true" @@ -85,6 +86,7 @@ services: # Utilize custom configs - ./development/idp-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php - ./development/idp-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + - ./development/announcement.php:/data/vendor/simplesamlphp/simplesamlphp/announcement/announcement.php # Utilize custom metadata - ./development/idp-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php @@ -102,7 +104,6 @@ services: bash -c "whenavail db 3306 60 /data/vendor/simplesamlphp/simplesamlphp/modules/silauth/lib/Auth/Source/yii migrate --interactive=0 && /data/run.sh" environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "a" SECRET_SALT: "not-secret-h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 1" @@ -117,7 +118,6 @@ services: PROFILE_URL_FOR_TESTS: "http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "default" MYSQL_HOST: "db" MYSQL_DATABASE: "silauth" MYSQL_USER: "silauth" @@ -142,13 +142,11 @@ services: - ./development/UserPass.php:/data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth/lib/Auth/Source/UserPass.php environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "b" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 2" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "material:material" ssp-idp3.local: build: . @@ -165,7 +163,6 @@ services: - ./development/idp3-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "c" SECRET_SALT: "h57fjem34fh*nsJFGNjweJ" SECURE_COOKIE: "false" @@ -188,7 +185,6 @@ services: # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "sp1" SECRET_SALT: "not-secret-h57fjemb&dn^nsJFGNjweJz1" SECURE_COOKIE: "false" @@ -210,7 +206,6 @@ services: - ./development/sp2-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php environment: - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: sp2 SECRET_SALT: h57fjemb&dn^nsJFGNjweJz2 SECURE_COOKIE: "false" @@ -232,7 +227,6 @@ services: - ./development/sp3-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php environment: - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: sp3 SECRET_SALT: h57fjemb&dn^nsJFGNjweJz3 SECURE_COOKIE: "false" @@ -253,7 +247,6 @@ services: # Utilize custom metadata - ./development/sp-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php environment: - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: sp1 IDP_NAME: THIS VARIABLE IS REQUIRED BUT PROBABLY NOT USED SECRET_SALT: NOT-a-secret-k49fjfkw73hjf9t87wjiw @@ -261,7 +254,6 @@ services: SHOW_SAML_ERRORS: "true" SAML20_IDP_ENABLE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" - THEME_USE: default # the broker and brokerDb containers are used by the silauth module broker: diff --git a/composer.json b/composer.json index 9db562a0..da7c6d39 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,6 @@ "simplesamlphp/composer-module-installer": "1.1.8", "rlanvin/php-ip": "^1.0", "silinternational/ssp-utilities": "^1.1.0", - "silinternational/simplesamlphp-module-material": "^8.1.1", - "silinternational/simplesamlphp-module-sildisco": "^4.0.0", "silinternational/php-env": "^3.1.0", "silinternational/psr3-adapters": "^3.1", "silinternational/yii2-json-log-targets": "^2.0", @@ -32,7 +30,8 @@ "fillup/fake-bower-assets": "^2.0", "google/recaptcha": "^1.1", "psr/log": "^1.0", - "monolog/monolog": "^1.22" + "monolog/monolog": "^1.22", + "aws/aws-sdk-php": "^3.313" }, "require-dev": { "behat/behat": "^3.8", @@ -46,7 +45,8 @@ "vendor/yiisoft/yii2/Yii.php" ], "psr-4": { - "Sil\\SspBase\\Features\\": "features/" + "Sil\\SspBase\\Features\\": "features/", + "Sil\\SilAuth\\migrations\\": "modules/silauth/migrations/" } }, "config": { diff --git a/composer.lock b/composer.lock index bfda34c2..d1ae0584 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ae5996cffda6f7fd16dea5eb0e2a1665", + "content-hash": "edb95cf120b3b42f8ba23bebd6b0f62a", "packages": [ { "name": "aws/aws-crt-php", - "version": "v1.2.2", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9" + "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9", - "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", + "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", "shasum": "" }, "require": { @@ -56,34 +56,35 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.5" }, - "time": "2023-07-20T16:49:55+00:00" + "time": "2024-04-19T21:30:56+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.269.0", + "version": "3.313.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78" + "reference": "2f5f173300888d6f630ce24751a6ee0f1e6d72e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", - "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2f5f173300888d6f630ce24751a6ee0f1e6d72e8", + "reference": "2f5f173300888d6f630ce24751a6ee0f1e6d72e8", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.4", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/promises": "^1.4.0 || ^2.0", "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -98,9 +99,8 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", - "psr/http-message": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" @@ -151,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.269.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.313.0" }, - "time": "2023-04-26T18:21:04+00:00" + "time": "2024-06-11T18:20:58+00:00" }, { "name": "cebe/markdown", @@ -1306,25 +1306,25 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -1332,7 +1332,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -1348,6 +1348,11 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -1361,9 +1366,9 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2023-08-25T10:54:48+00:00" }, { "name": "paragonie/random_compat", @@ -2842,93 +2847,6 @@ }, "time": "2022-08-24T14:44:38+00:00" }, - { - "name": "silinternational/simplesamlphp-module-material", - "version": "8.1.1", - "source": { - "type": "git", - "url": "https://github.com/silinternational/simplesamlphp-module-material.git", - "reference": "0cb61d2fa2be01f72ab1b44970c2d5c344b32066" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/simplesamlphp-module-material/zipball/0cb61d2fa2be01f72ab1b44970c2d5c344b32066", - "reference": "0cb61d2fa2be01f72ab1b44970c2d5c344b32066", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.0", - "silinternational/ssp-utilities": "^1.0", - "simplesamlphp/composer-module-installer": "^1.1.5", - "simplesamlphp/simplesamlphp": "~1.18.6 || ~1.19.0" - }, - "require-dev": { - "roave/security-advisories": "dev-master" - }, - "type": "simplesamlphp-module", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Billy Clark", - "email": "billy_clark@sil.org" - } - ], - "description": "Material Design theme for IdP Hub based on SimpleSAMLphp", - "support": { - "issues": "https://github.com/silinternational/simplesamlphp-module-material/issues", - "source": "https://github.com/silinternational/simplesamlphp-module-material/tree/8.1.1" - }, - "time": "2023-06-12T17:37:14+00:00" - }, - { - "name": "silinternational/simplesamlphp-module-sildisco", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/silinternational/simplesamlphp-module-sildisco.git", - "reference": "b9586d375272108d3006ae3df73ca4379c5f9353" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/simplesamlphp-module-sildisco/zipball/b9586d375272108d3006ae3df73ca4379c5f9353", - "reference": "b9586d375272108d3006ae3df73ca4379c5f9353", - "shasum": "" - }, - "require": { - "aws/aws-sdk-php": "^3.0", - "ext-dom": "*", - "php": ">=7.0", - "silinternational/php-env": "^1.0||^2.0||^3.0", - "silinternational/ssp-utilities": "^1.0", - "simplesamlphp/composer-module-installer": "^1.1.5", - "simplesamlphp/simplesamlphp": "~1.18.6||^1.19" - }, - "require-dev": { - "phpunit/phpunit": "^8.4", - "roave/security-advisories": "dev-master" - }, - "type": "simplesamlphp-module", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Phillip Shipley", - "email": "phillip.shipley@gmail.com" - } - ], - "description": "IdP Discovery module for simpleSAMLphp with extra business logic", - "support": { - "issues": "https://github.com/silinternational/simplesamlphp-module-sildisco/issues", - "source": "https://github.com/silinternational/simplesamlphp-module-sildisco/tree/4.0.0" - }, - "time": "2023-04-25T18:31:08+00:00" - }, { "name": "silinternational/ssp-utilities", "version": "1.1.0", diff --git a/development/announcement.php b/development/announcement.php new file mode 100644 index 00000000..6c0b4122 --- /dev/null +++ b/development/announcement.php @@ -0,0 +1,18 @@ + "2017-12-20 01:02:03", // "Y-m-d H:i:s", + * 'end_datetime' => "2017-12-24 01:02:03", // "Y-m-d H:i:s", + * 'announcement_text' => '

Notice:

Christmas is almost here!
', + * ]; + */ + +return [ + 'start_datetime' => "2016-12-20 01:02:03", // optional + 'end_datetime' => "2099-12-30 01:02:03", // optional + 'announcement_text' => "

Information

+
This is a test announcement.
", +]; diff --git a/development/hub/metadata/idp-remote.php b/development/hub/metadata/idp-remote.php index 56ecd8d6..81c89b2a 100644 --- a/development/hub/metadata/idp-remote.php +++ b/development/hub/metadata/idp-remote.php @@ -29,7 +29,7 @@ // NOTE: This breaks being able to test the hub's authentication sources // since the hub doesn't create an SP entry in the session - 'SPList' => ['http://ssp-sp1.local', 'http://ssp-sp2.local', 'http://ssp-sp3.local'], + 'SPList' => ['http://ssp-sp1.local:8081', 'http://ssp-sp2.local:8082', 'http://ssp-sp3.local:8083'], ], 'http://ssp-idp1.local' => [ 'metadata-set' => 'saml20-idp-remote', diff --git a/development/idp-local/config/authsources.php b/development/idp-local/config/authsources.php index 9971fe63..2fb95e1b 100644 --- a/development/idp-local/config/authsources.php +++ b/development/idp-local/config/authsources.php @@ -86,6 +86,9 @@ 'mail' => ['missing_exp@example.com'], 'employeeNumber' => ['44444'], 'cn' => ['MISSING_EXP'], + 'mfa' => [ + 'prompt' => 'no', + ], ], // expirychecker test user whose password expiry is invalid diff --git a/development/idp-local/config/config.php b/development/idp-local/config/config.php index db650c58..3511775c 100644 --- a/development/idp-local/config/config.php +++ b/development/idp-local/config/config.php @@ -22,11 +22,9 @@ try { // Required to be defined in environment variables - $ADMIN_EMAIL = Env::requireEnv('ADMIN_EMAIL'); $ADMIN_PASS = Env::requireEnv('ADMIN_PASS'); $SECRET_SALT = Env::requireEnv('SECRET_SALT'); $IDP_NAME = Env::requireEnv('IDP_NAME'); - $IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); } catch (EnvVarNotFoundException $e) { // Return error response code/message to HTTP request. @@ -41,9 +39,10 @@ exit($responseContent); } - // Defaults provided if not defined in environment +$IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); $BASE_URL_PATH = Env::get('BASE_URL_PATH', '/'); +$ADMIN_EMAIL = Env::get('ADMIN_EMAIL', 'na@example.org'); $ADMIN_NAME = Env::get('ADMIN_NAME', 'SAML Admin'); $ADMIN_PROTECT_INDEX_PAGE = Env::get('ADMIN_PROTECT_INDEX_PAGE', true); $SHOW_SAML_ERRORS = Env::get('SHOW_SAML_ERRORS', false); @@ -51,7 +50,6 @@ $ENABLE_DEBUG = Env::get('ENABLE_DEBUG', false); $LOGGING_LEVEL = Env::get('LOGGING_LEVEL', 'NOTICE'); $LOGGING_HANDLER = Env::get('LOGGING_HANDLER', 'stderr'); -$THEME_USE = Env::get('THEME_USE', 'material:material'); // Options: https://github.com/silinternational/simplesamlphp-module-material/blob/develop/README.md#branding $THEME_COLOR_SCHEME = Env::get('THEME_COLOR_SCHEME', null); @@ -964,7 +962,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* * Set this option to the text you would like to appear at the header of each page. Set to false if you don't want diff --git a/development/idp2-local/config/config.php b/development/idp2-local/config/config.php index db650c58..3511775c 100644 --- a/development/idp2-local/config/config.php +++ b/development/idp2-local/config/config.php @@ -22,11 +22,9 @@ try { // Required to be defined in environment variables - $ADMIN_EMAIL = Env::requireEnv('ADMIN_EMAIL'); $ADMIN_PASS = Env::requireEnv('ADMIN_PASS'); $SECRET_SALT = Env::requireEnv('SECRET_SALT'); $IDP_NAME = Env::requireEnv('IDP_NAME'); - $IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); } catch (EnvVarNotFoundException $e) { // Return error response code/message to HTTP request. @@ -41,9 +39,10 @@ exit($responseContent); } - // Defaults provided if not defined in environment +$IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); $BASE_URL_PATH = Env::get('BASE_URL_PATH', '/'); +$ADMIN_EMAIL = Env::get('ADMIN_EMAIL', 'na@example.org'); $ADMIN_NAME = Env::get('ADMIN_NAME', 'SAML Admin'); $ADMIN_PROTECT_INDEX_PAGE = Env::get('ADMIN_PROTECT_INDEX_PAGE', true); $SHOW_SAML_ERRORS = Env::get('SHOW_SAML_ERRORS', false); @@ -51,7 +50,6 @@ $ENABLE_DEBUG = Env::get('ENABLE_DEBUG', false); $LOGGING_LEVEL = Env::get('LOGGING_LEVEL', 'NOTICE'); $LOGGING_HANDLER = Env::get('LOGGING_HANDLER', 'stderr'); -$THEME_USE = Env::get('THEME_USE', 'material:material'); // Options: https://github.com/silinternational/simplesamlphp-module-material/blob/develop/README.md#branding $THEME_COLOR_SCHEME = Env::get('THEME_COLOR_SCHEME', null); @@ -964,7 +962,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* * Set this option to the text you would like to appear at the header of each page. Set to false if you don't want diff --git a/development/idp3-local/config/config.php b/development/idp3-local/config/config.php index 656009fa..6673268b 100644 --- a/development/idp3-local/config/config.php +++ b/development/idp3-local/config/config.php @@ -27,7 +27,6 @@ $SESSION_COOKIE_LIFETIME = (int)(Env::get('SESSION_COOKIE_LIFETIME', 0)); $SESSION_REMEMBERME_LIFETIME = (int)(Env::get('SESSION_REMEMBERME_LIFETIME', (14 * 86400))); // 14 days $SECURE_COOKIE = Env::get('SECURE_COOKIE', true); -$THEME_USE = Env::get('THEME_USE', 'default'); $MEMCACHE_STORE_EXPIRES = (int)(Env::get('MEMCACHE_STORE_EXPIRES', (36 * 60 * 60))); // 36 hours. $SAML20_IDP_ENABLE = Env::get('SAML20_IDP_ENABLE', true); $GOOGLE_ENABLE = Env::get('GOOGLE_ENABLE', false); @@ -445,7 +444,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* diff --git a/development/hub/run-debug.sh b/development/run-debug.sh similarity index 100% rename from development/hub/run-debug.sh rename to development/run-debug.sh diff --git a/development/sp-local/config/config.php b/development/sp-local/config/config.php index 4bd8af87..2608cec5 100644 --- a/development/sp-local/config/config.php +++ b/development/sp-local/config/config.php @@ -37,7 +37,6 @@ $SESSION_COOKIE_LIFETIME = (int)(Env::get('SESSION_COOKIE_LIFETIME', 0)); $SESSION_REMEMBERME_LIFETIME = (int)(Env::get('SESSION_REMEMBERME_LIFETIME', (14 * 86400))); // 14 days $SECURE_COOKIE = Env::get('SECURE_COOKIE', true); -$THEME_USE = Env::get('THEME_USE', 'default'); $SAML20_IDP_ENABLE = Env::get('SAML20_IDP_ENABLE', true); $GOOGLE_ENABLE = Env::get('GOOGLE_ENABLE', false); @@ -486,7 +485,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* diff --git a/development/sp2-local/config/config.php b/development/sp2-local/config/config.php index 3c42682a..f1b6c903 100644 --- a/development/sp2-local/config/config.php +++ b/development/sp2-local/config/config.php @@ -37,7 +37,6 @@ $SESSION_COOKIE_LIFETIME = (int)(Env::get('SESSION_COOKIE_LIFETIME', 0)); $SESSION_REMEMBERME_LIFETIME = (int)(Env::get('SESSION_REMEMBERME_LIFETIME', (14 * 86400))); // 14 days $SECURE_COOKIE = Env::get('SECURE_COOKIE', true); -$THEME_USE = Env::get('THEME_USE', 'default'); $SAML20_IDP_ENABLE = Env::get('SAML20_IDP_ENABLE', true); $GOOGLE_ENABLE = Env::get('GOOGLE_ENABLE', false); @@ -486,7 +485,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* diff --git a/development/sp3-local/config/config.php b/development/sp3-local/config/config.php index 1686e5a2..df03c377 100644 --- a/development/sp3-local/config/config.php +++ b/development/sp3-local/config/config.php @@ -37,7 +37,6 @@ $SESSION_COOKIE_LIFETIME = (int)(Env::get('SESSION_COOKIE_LIFETIME', 0)); $SESSION_REMEMBERME_LIFETIME = (int)(Env::get('SESSION_REMEMBERME_LIFETIME', (14 * 86400))); // 14 days $SECURE_COOKIE = Env::get('SECURE_COOKIE', true); -$THEME_USE = Env::get('THEME_USE', 'default'); $SAML20_IDP_ENABLE = Env::get('SAML20_IDP_ENABLE', true); $GOOGLE_ENABLE = Env::get('GOOGLE_ENABLE', false); @@ -486,7 +485,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* diff --git a/docker-compose.yml b/docker-compose.yml index 267dbfa8..08e1ca6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,10 @@ services: PMA_PASSWORD: silauth test: - build: . + build: + context: . + args: + COMPOSER_FLAGS: "--no-interaction --no-progress" depends_on: - ssp-hub.local - ssp-idp1.local @@ -37,7 +40,6 @@ services: MYSQL_PASSWORD: silauth COMPOSER_CACHE_DIR: /composer PROFILE_URL_FOR_TESTS: http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: b SECRET_SALT: abc123 IDP_NAME: x @@ -96,6 +98,7 @@ services: # Utilize custom configs - ./development/hub/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + - ./development/announcement.php:/data/vendor/simplesamlphp/simplesamlphp/announcement/announcement.php # Utilize custom metadata - ./development/hub/metadata/idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/idp-remote.php @@ -103,9 +106,6 @@ services: - ./development/hub/metadata/saml20-sp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-hosted.php - ./development/hub/metadata/sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/sp-remote.php - # Configure the debugger - - ./development/hub/run-debug.sh:/data/run-debug.sh - # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh @@ -116,18 +116,15 @@ services: - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material - command: /data/run-debug.sh ports: - "80:80" environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "abc123" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "Hub" SECURE_COOKIE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "material:material" THEME_COLOR_SCHEME: "orange-light_blue" HUB_MODE: "true" @@ -142,6 +139,7 @@ services: # Utilize custom configs - ./development/idp-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php - ./development/idp-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + - ./development/announcement.php:/data/vendor/simplesamlphp/simplesamlphp/announcement/announcement.php # Utilize custom metadata - ./development/idp-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php @@ -169,7 +167,6 @@ services: ports: - "8085:80" environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "a" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 1" @@ -184,7 +181,6 @@ services: PROFILE_URL_FOR_TESTS: "http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "default" SESSION_STORE_TYPE: "sql" MYSQL_HOST: "db" MYSQL_DATABASE: "silauth" @@ -219,13 +215,11 @@ services: ports: - "8086:80" environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "b" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 2" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "material:material" ssp-idp3.local: build: . @@ -253,7 +247,6 @@ services: env_file: - local.env environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "c" SECRET_SALT: "h57fjem34fh*nsJFGNjweJ" SECURE_COOKIE: "false" @@ -286,7 +279,6 @@ services: ports: - "8081:80" environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "sp1" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJz1" SECURE_COOKIE: "false" @@ -317,7 +309,6 @@ services: ports: - "8082:80" environment: - ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "sp2" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJz2" SECURE_COOKIE: "false" @@ -342,7 +333,6 @@ services: env_file: - local.env environment: - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: sp3 SECRET_SALT: h57fjemb&dn^nsJFGNjweJz3 SECURE_COOKIE: "false" @@ -364,7 +354,6 @@ services: ports: - "8084:80" environment: - ADMIN_EMAIL: john_doe@there.com ADMIN_PASS: sp1 IDP_NAME: THIS VARIABLE IS REQUIRED BUT PROBABLY NOT USED SECRET_SALT: NOT-a-secret-k49fjfkw73hjf9t87wjiw @@ -372,7 +361,6 @@ services: SHOW_SAML_ERRORS: "true" SAML20_IDP_ENABLE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" - THEME_USE: material:material # the broker and brokerDb containers are used by the silauth module broker: diff --git a/dockerbuild/config/config.php b/dockerbuild/config/config.php index 7f0280c6..74da5399 100644 --- a/dockerbuild/config/config.php +++ b/dockerbuild/config/config.php @@ -22,11 +22,9 @@ try { // Required to be defined in environment variables - $ADMIN_EMAIL = Env::requireEnv('ADMIN_EMAIL'); $ADMIN_PASS = Env::requireEnv('ADMIN_PASS'); $SECRET_SALT = Env::requireEnv('SECRET_SALT'); $IDP_NAME = Env::requireEnv('IDP_NAME'); - $IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); } catch (EnvVarNotFoundException $e) { // Return error response code/message to HTTP request. @@ -41,9 +39,10 @@ exit($responseContent); } - // Defaults provided if not defined in environment +$IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); $BASE_URL_PATH = Env::get('BASE_URL_PATH', '/'); +$ADMIN_EMAIL = Env::get('ADMIN_EMAIL', 'na@example.org'); $ADMIN_NAME = Env::get('ADMIN_NAME', 'SAML Admin'); $ADMIN_PROTECT_INDEX_PAGE = Env::get('ADMIN_PROTECT_INDEX_PAGE', true); $SHOW_SAML_ERRORS = Env::get('SHOW_SAML_ERRORS', false); @@ -51,7 +50,6 @@ $ENABLE_DEBUG = Env::get('ENABLE_DEBUG', false); $LOGGING_LEVEL = Env::get('LOGGING_LEVEL', 'NOTICE'); $LOGGING_HANDLER = Env::get('LOGGING_HANDLER', 'stderr'); -$THEME_USE = Env::get('THEME_USE', 'material:material'); // Options: https://github.com/silinternational/simplesamlphp-module-material/blob/develop/README.md#branding $THEME_COLOR_SCHEME = Env::get('THEME_COLOR_SCHEME', null); @@ -333,9 +331,8 @@ * SAML messages will be logged, including plaintext versions of encrypted * messages. * - * - 'backtraces': this action controls the logging of error backtraces. If you - * want to log backtraces so that you can debug any possible errors happening in - * SimpleSAMLphp, enable this action (add it to the array or set it to true). + * - 'backtraces': this action controls the logging of error backtraces so you + * can debug any possible errors happening in SimpleSAMLphp. * * - 'validatexml': this action allows you to validate SAML documents against all * the relevant XML schemas. SAML 1.1 messages or SAML metadata parsed with @@ -556,6 +553,8 @@ * Which functionality in SimpleSAMLphp do you want to enable. Normally you would enable only * one of the functionalities below, but in some cases you could run multiple functionalities. * In example when you are setting up a federation bridge. + * + * Note that shib13-idp has been deprecated and will be removed in SimpleSAMLphp 2.0. */ 'enable.saml20-idp' => $SAML20_IDP_ENABLE, 'enable.shib13-idp' => false, @@ -676,6 +675,13 @@ * the RFC6265bis SameSite cookie attribute. If set to null, no SameSite * attribute will be sent. * + * A value of "None" is required to properly support cross-domain POST + * requests which are used by different SAML bindings. Because some older + * browsers do not support this value, the canSetSameSiteNone function + * can be called to only set it for compatible browsers. + * + * You must also set the 'session.cookie.secure' value above to true. + * * Example: * 'session.cookie.samesite' => 'None', */ @@ -875,7 +881,7 @@ * ], * * establishing that if a translation for the "no" language code is - * not available, we look for translations in "nb" (Norwegian Bokmål), + * not available, we look for translations in "nb", * and so on, in that order. */ 'priorities' => [ @@ -885,6 +891,8 @@ 'se' => ['nb', 'no', 'nn', 'en'], 'nr' => ['zu', 'en'], 'nd' => ['zu', 'en'], + 'tw' => ['st', 'en'], + 'nso' => ['st', 'en'], ], ], @@ -894,7 +902,7 @@ 'language.available' => [ 'en', 'no', 'nn', 'se', 'da', 'de', 'sv', 'fi', 'es', 'ca', 'fr', 'it', 'nl', 'lb', 'cs', 'sl', 'lt', 'hr', 'hu', 'pl', 'pt', 'pt-br', 'tr', 'ja', 'zh', 'zh-tw', 'ru', - 'et', 'he', 'id', 'sr', 'lv', 'ro', 'eu', 'el', 'af', 'zu', 'xh', + 'et', 'he', 'id', 'sr', 'lv', 'ro', 'eu', 'el', 'af', 'zu', 'xh', 'st', ], 'language.rtl' => ['ar', 'dv', 'fa', 'ur', 'he'], 'language.default' => 'en', @@ -911,10 +919,10 @@ 'language.cookie.name' => 'language', 'language.cookie.domain' => null, 'language.cookie.path' => '/', - 'language.cookie.secure' => false, + 'language.cookie.secure' => true, 'language.cookie.httponly' => false, 'language.cookie.lifetime' => (60 * 60 * 24 * 900), - 'language.cookie.samesite' => null, + 'language.cookie.samesite' => \SimpleSAML\Utils\HTTP::canSetSameSiteNone() ? 'None' : null, /** * Custom getLanguage function called from SimpleSAML\Locale\Language::getLanguage(). @@ -963,7 +971,7 @@ /* * Which theme directory should be used? */ - 'theme.use' => $THEME_USE, + 'theme.use' => 'material:material', /* * Set this option to the text you would like to appear at the header of each page. Set to false if you don't want @@ -1036,7 +1044,7 @@ ], /* - * If using the material theme, which color scheme to use + * color scheme to use for the material theme * Options: https://github.com/silinternational/simplesamlphp-module-material/blob/develop/README.md#branding */ 'theme.color-scheme' => $THEME_COLOR_SCHEME, @@ -1119,6 +1127,7 @@ ], // 48 => *** WARNING: For Hubs this entry is added at the end of this file + // 49 => *** WARNING: For Hubs this entry is added at the end of this file // If no attributes are requested in the SP metadata, then these will be sent through diff --git a/dockerbuild/run-idp.sh b/dockerbuild/run-idp.sh index d08922d0..2f65d0f0 100755 --- a/dockerbuild/run-idp.sh +++ b/dockerbuild/run-idp.sh @@ -7,10 +7,10 @@ set -x set -e # Try to run database migrations -cd /data/vendor/simplesamlphp/simplesamlphp/modules/silauth/lib/Auth/Source -chmod a+x ./yii +cd /data/vendor/simplesamlphp/simplesamlphp +chmod a+x ./modules/silauth/lib/Auth/Source/yii -./yii migrate --interactive=0 +./modules/silauth/lib/Auth/Source/yii migrate --interactive=0 cd /data ./run.sh diff --git a/dockerbuild/run-integration-tests.sh b/dockerbuild/run-integration-tests.sh index b3d92545..61c751f7 100755 --- a/dockerbuild/run-integration-tests.sh +++ b/dockerbuild/run-integration-tests.sh @@ -7,7 +7,6 @@ set -x set -e cd /data -export COMPOSER_ALLOW_SUPERUSER=1; composer install whenavail "ssp-hub.local" 80 15 echo Hub ready whenavail "ssp-idp1.local" 80 5 echo IDP 1 ready diff --git a/docs/material_tests.md b/docs/material_tests.md index 5e287bd1..fa4fc7eb 100644 --- a/docs/material_tests.md +++ b/docs/material_tests.md @@ -175,9 +175,9 @@ _NOTE: At this time, the correct code is not known and can't be tested locally ( ## Announcements functionality -1. Goto [SP 2](http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) 1. The announcement should be displayed on the hub -1. Click **idp3** (first one) +1. Click **idp1** (first one) 1. The announcement should be displayed at the login screen ## SP name functionality diff --git a/features/Sp1Idp1Sp2Idp2Sp3.feature b/features/Sp1Idp1Sp2Idp2Sp3.feature index be765055..6498fa23 100644 --- a/features/Sp1Idp1Sp2Idp2Sp3.feature +++ b/features/Sp1Idp1Sp2Idp2Sp3.feature @@ -25,9 +25,9 @@ Feature: Ensure I can login to Sp1 through Idp1, must login to Sp2 through Idp2 Scenario: Logout of IDP1 Given I have authenticated with IDP1 for SP1 When I log out of IDP1 - Then I should see "You have been logged out." + Then I should see "You have now been logged out." Scenario: Logout of IDP2 Given I have authenticated with IDP2 for SP2 When I log out of IDP2 - Then I should see "You have been logged out." + Then I should see "You have now been logged out." diff --git a/features/bootstrap/ExpiryContext.php b/features/bootstrap/ExpiryContext.php index 749c134b..358e1767 100644 --- a/features/bootstrap/ExpiryContext.php +++ b/features/bootstrap/ExpiryContext.php @@ -152,7 +152,7 @@ public function iProvideCredentialsThatHaveNoPasswordExpirationDate() public function iShouldSeeAnErrorMessage() { $page = $this->session->getPage(); - Assert::assertContains('Unhandled exception', $page->getHtml()); + Assert::assertContains('An error occurred', $page->getHtml()); } /** diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 748733c7..2734d304 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -22,6 +22,8 @@ class FeatureContext extends MinkContext protected const SP2_LOGIN_PAGE = 'http://ssp-sp2.local/module.php/core/authenticate.php?as=ssp-hub'; protected const SP3_LOGIN_PAGE = 'http://ssp-sp3.local/module.php/core/authenticate.php?as=ssp-hub'; + const SCREENSHOTS_PATH = '/data/features/screenshots/'; + /** @var Session */ protected $session; @@ -42,11 +44,27 @@ public function __construct() /** @AfterStep */ public function afterStep(AfterStepScope $scope) { - if (! $scope->getTestResult()->getResultCode() === StepResult::FAILED) { + if ($scope->getTestResult()->getResultCode() === StepResult::FAILED) { $this->showPageDetails(); + $this->takeScreenshot(); } } - + + /** + * Store a screenshot. + */ + private function takeScreenshot() { + $screenshot = $this->getSession()->getDriver()->getScreenshot(); + if (!is_dir(self::SCREENSHOTS_PATH)) { + mkdir(self::SCREENSHOTS_PATH); + } + if (is_dir(self::SCREENSHOTS_PATH)) { + $path = self::SCREENSHOTS_PATH . date('d-m-y') . '-' . uniqid() . '.png'; + file_put_contents($path, $screenshot); + print "\n\nScreenshot: " . $path; + } + } + protected function showPageDetails() { echo '[' . $this->session->getStatusCode() . '] '; @@ -251,9 +269,8 @@ public function iLogIn() $this->submitLoginForm($page); } catch (ElementNotFoundException $e) { Assert::true(false, sprintf( - "Did not find that element in the page.\nError: %s\nPage content: %s", - $e->getMessage(), - $page->getContent() + "Did not find that element in the page.\nError: %s", + $e->getMessage() )); } } diff --git a/features/bootstrap/MfaContext.php b/features/bootstrap/MfaContext.php index 16dbdaa0..988f1fb6 100644 --- a/features/bootstrap/MfaContext.php +++ b/features/bootstrap/MfaContext.php @@ -150,7 +150,7 @@ public function iShouldSeeAPromptForABackupCode() { $page = $this->session->getPage(); $pageHtml = $page->getHtml(); - Assert::assertContains('

Printable Backup Code

', $pageHtml); + Assert::assertContains('Printable code', $pageHtml); Assert::assertContains('Enter code', $pageHtml); } @@ -171,7 +171,7 @@ public function iShouldSeeAPromptForATotpCode() { $page = $this->session->getPage(); $pageHtml = $page->getHtml(); - Assert::assertContains('

Smartphone App

', $pageHtml); + Assert::assertContains('Authenticator app', $pageHtml); Assert::assertContains('Enter 6-digit code', $pageHtml); } @@ -191,7 +191,7 @@ public function iProvideCredentialsThatNeedMfaAndHaveUfAvailable() public function iShouldSeeAPromptForAWebAuthn() { $page = $this->session->getPage(); - Assert::assertContains('

USB Security Key

', $page->getHtml()); + Assert::assertContains('Security key', $page->getHtml()); } protected function submitMfaValue($mfaValue) @@ -207,8 +207,10 @@ protected function submitMfaValue($mfaValue) */ public function iSubmitACorrectBackupCode() { - if (! $this->pageContainsElementWithText('h2', 'Printable Backup Code')) { - $this->clickLink('backupcode'); + if (! $this->pageContainsElementWithText('h1', 'Printable code')) { + // find image of the backup code option presented in other_mfas.php + $printableCodeOption = $this->session->getPage()->find('css', 'img[src=mfa-backupcode\002Esvg]'); + $printableCodeOption->click(); } $this->submitMfaValue(FakeIdBrokerClient::CORRECT_VALUE); } @@ -344,7 +346,7 @@ public function iShouldSeeAMessageThatIAmRunningLowOnBackupCodes() { $page = $this->session->getPage(); Assert::assertContains( - 'You are almost out of Printable Backup Codes', + 'Almost out of printable codes', $page->getHtml() ); } @@ -375,7 +377,7 @@ public function iShouldSeeAMessageThatIHaveUsedUpMyBackupCodes() { $page = $this->session->getPage(); Assert::assertContains( - 'You just used your last Printable Backup Code', + 'Last printable code used', $page->getHtml() ); } @@ -405,7 +407,7 @@ public function iShouldBeToldIOnlyHaveBackupCodesLeft($numRemaining) { $page = $this->session->getPage(); Assert::assertContains( - 'You only have ' . $numRemaining . ' remaining', + 'You only have ' . $numRemaining . ' more left', $page->getHtml() ); } @@ -417,7 +419,7 @@ public function iShouldBeGivenMoreBackupCodes() { $page = $this->session->getPage(); Assert::assertContains( - 'Here are your new Printable Backup Codes', + 'New printable codes', $page->getContent() ); } @@ -610,7 +612,7 @@ public function theUserHasAManagerEmail() public function iShouldSeeALinkToSendACodeToTheUsersManager() { $page = $this->session->getPage(); - Assert::assertContains('Can\'t use any of your 2-Step Verification options', $page->getContent()); + Assert::assertContains('I need help', $page->getContent()); } /** @@ -637,7 +639,9 @@ public function iShouldNotSeeALinkToSendACodeToTheUsersManager() */ public function iClickTheRequestAssistanceLink() { - $this->clickLink('Click here'); + // find image of the recovery contact option presented in prompt_for_mfa_manager.php + $printableCodeOption = $this->session->getPage()->find('css', 'img[src=mfa-manager\002Esvg]'); + $printableCodeOption->click(); } /** @@ -655,7 +659,7 @@ public function iShouldSeeAPromptForAManagerRescueCode() { $page = $this->session->getPage(); $pageHtml = $page->getHtml(); - Assert::assertContains('

Manager Rescue Code

', $pageHtml); + Assert::assertContains('Ask Your Recovery Contact for Help', $pageHtml); Assert::assertContains('Enter code', $pageHtml); } diff --git a/features/bootstrap/ProfileReviewContext.php b/features/bootstrap/ProfileReviewContext.php index 9c88f526..5312b09e 100644 --- a/features/bootstrap/ProfileReviewContext.php +++ b/features/bootstrap/ProfileReviewContext.php @@ -57,7 +57,7 @@ protected function submitFormByClickingButtonNamed($buttonName) */ public function iProvideCredentialsThatDoNotNeedReview() { - // See `development/idp-local/config/authsources.php` for options. + // Credentials defined in `development/idp-local/config/authsources.php` $this->username = 'no_review'; $this->password = 'e'; } @@ -67,7 +67,7 @@ public function iProvideCredentialsThatDoNotNeedReview() */ public function iProvideCredentialsThatAreDueForAReminder($category, $nagType) { - // See `development/idp-local/config/authsources.php` for options. + // Credentials defined in `development/idp-local/config/authsources.php` $this->username = $category . '_' . $nagType; switch ($this->username) { case 'mfa_add': @@ -77,13 +77,19 @@ public function iProvideCredentialsThatAreDueForAReminder($category, $nagType) case 'method_add': $this->password = 'g'; break; - - case 'profile_review': - $this->password = 'h'; - break; } } + /** + * @Given I provide credentials that are due for a profile review + */ + public function iProvideCredentialsThatAreDueForAProfileReview() + { + // Credentials defined in `development/idp-local/config/authsources.php` + $this->username = 'profile_review'; + $this->password = 'h'; + } + protected function pageContainsElementWithText($cssSelector, $text) { @@ -122,6 +128,14 @@ public function iClickTheUpdateProfileButton() $this->submitFormByClickingButtonNamed('update'); } + /** + * @When I click the :text link + */ + public function iClickTheLink($text) + { + $this->clickLink($text); + } + /** * @Then I should end up at the update profile URL */ @@ -137,6 +151,29 @@ public function iShouldEndUpAtTheUpdateProfileUrl() ); } + /** + * @Then I should end up at the update profile URL on a new tab + */ + public function iShouldEndUpAtTheUpdateProfileUrlOnANewTab() + { + $profileUrl = Env::get('PROFILE_URL_FOR_TESTS'); + Assert::assertNotEmpty($profileUrl, 'No PROFILE_URL_FOR_TESTS provided'); + + $windowNames = $this->session->getWindowNames(); + Assert::assertGreaterThanOrEqual(2, sizeof($windowNames), + 'Expected to see at least 2 windows opened'); + + foreach ($windowNames as $windowName) { + $this->session->switchToWindow($windowName); + $currentUrl = $this->session->getCurrentUrl(); + if ($currentUrl == $profileUrl) { + return; + } + } + + Assert::fail('Did NOT end up at the update profile URL'); + } + /** * @Then I should see the message: :message */ @@ -155,6 +192,15 @@ public function thereShouldBeAWayToGoUpdateMyProfileNow() $this->assertFormContains('name="update"', $page); } + /** + * @Then there should be a way to go review my profile now + */ + public function thereShouldBeAWayToGoReviewMyProfileNow() + { + $page = $this->session->getPage(); + Assert::assertContains('Some of these need updating', $page->getHtml()); + } + /** * @Given I provide credentials for a user that has used the manager mfa option */ diff --git a/features/bootstrap/SilDiscoContext.php b/features/bootstrap/SilDiscoContext.php index 24781840..b277f256 100644 --- a/features/bootstrap/SilDiscoContext.php +++ b/features/bootstrap/SilDiscoContext.php @@ -86,7 +86,7 @@ public function iLogOutOfIdp1() $this->iGoToTheSpLoginPage('SP3'); $this->iClickOnTheTile('IDP 1'); $this->clickLink('Logout'); - $this->assertPageContainsText('You have been logged out.'); + $this->assertPageContainsText('You have now been logged out.'); } /** @@ -96,7 +96,7 @@ public function iLogOutOfIdp2() { $this->iGoToTheSpLoginPage('SP2'); $this->clickLink('Logout'); - $this->assertPageContainsText('You have been logged out.'); + $this->assertPageContainsText('You have now been logged out.'); } /** diff --git a/features/fakes/FakeIdBrokerClient.php b/features/fakes/FakeIdBrokerClient.php index 31899b3a..c404fb9e 100644 --- a/features/fakes/FakeIdBrokerClient.php +++ b/features/fakes/FakeIdBrokerClient.php @@ -12,7 +12,7 @@ class FakeIdBrokerClient const CORRECT_VALUE = '111111'; const INCORRECT_VALUE = '999999'; - const RATE_LIMITED_MFA_ID = '987'; + const RATE_LIMITED_MFA_ID = 987; /** * Constructor. @@ -32,12 +32,9 @@ public function __construct( /** * Verify an MFA value - * @param int $id - * @param string $value - * @return array * @throws ServiceException */ - public function mfaVerify($id, $employeeId, $value) + public function mfaVerify(int $id, string $employeeId, string|array $value):array { if ($id === self::RATE_LIMITED_MFA_ID) { throw new ServiceException('Too many recent failures for this MFA', 0, 429); diff --git a/features/profilereview.feature b/features/profilereview.feature index 9c3988d1..be4f84e3 100644 --- a/features/profilereview.feature +++ b/features/profilereview.feature @@ -16,10 +16,16 @@ Feature: Prompt to review profile information And there should be a way to continue to my intended destination Examples: - | category | nag type | message | - | mfa | add | "2-Step Verification" | - | method | add | "alternate email addresses" | - | profile | review | "Please take a moment to review" | + | category | nag type | message | + | mfa | add | "2-Step Verification" | + | method | add | "alternate email address" | + + Scenario: Present profile review as required by the user profile + Given I provide credentials that are due for a profile review + When I log in + Then I should see the message: "Are these still correct?" + And there should be a way to go review my profile now + And there should be a way to continue to my intended destination Scenario Outline: Obeying a reminder Given I provide credentials that are due for a reminder @@ -31,7 +37,12 @@ Feature: Prompt to review profile information | category | nag type | | mfa | add | | method | add | - | profile | review | + + Scenario: Obeying a profile review reminder + Given I provide credentials that are due for a profile review + And I have logged in + When I click the "Some of these need updating" link + Then I should end up at the update profile URL on a new tab Scenario Outline: Ignoring a reminder Given I provide credentials that are due for a reminder @@ -43,10 +54,15 @@ Feature: Prompt to review profile information | category | nag type | | mfa | add | | method | add | - | profile | review | + + Scenario: Ignoring a profile review reminder + Given I provide credentials that are due for a profile review + And I have logged in + When I click the remind-me-later button + Then I should end up at my intended destination Scenario: Ensuring that manager mfa data is not displayed to the user Given I provide credentials for a user that has used the manager mfa option And I have logged in - Then I should see the message: "Please take a moment to review" + Then I should see the message: "Are these still correct?" And I should not see any manager mfa information diff --git a/installed-packages.json b/installed-packages.json index 252d82c9..7347d5ab 100644 --- a/installed-packages.json +++ b/installed-packages.json @@ -1,11 +1,11 @@ [ { "name": "aws/aws-crt-php", - "version": "v1.2.2" + "version": "v1.2.5" }, { "name": "aws/aws-sdk-php", - "version": "3.269.0" + "version": "3.313.0" }, { "name": "cebe/markdown", @@ -17,7 +17,7 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.16.0" + "version": "v4.17.0" }, { "name": "fillup/fake-bower-assets", @@ -69,7 +69,7 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1" + "version": "2.7.0" }, { "name": "paragonie/random_compat", @@ -115,10 +115,6 @@ "name": "ralouphie/getallheaders", "version": "3.0.3" }, - { - "name": "ramsey/uuid", - "version": "3.9.7" - }, { "name": "rlanvin/php-ip", "version": "v1.0.1" @@ -133,7 +129,7 @@ }, { "name": "silinternational/idp-id-broker-php-client", - "version": "4.3.1" + "version": "4.3.2" }, { "name": "silinternational/php-env", @@ -143,30 +139,6 @@ "name": "silinternational/psr3-adapters", "version": "3.1.0" }, - { - "name": "silinternational/simplesamlphp-module-expirychecker", - "version": "3.1.3" - }, - { - "name": "silinternational/simplesamlphp-module-material", - "version": "8.1.1" - }, - { - "name": "silinternational/simplesamlphp-module-mfa", - "version": "5.2.1" - }, - { - "name": "silinternational/simplesamlphp-module-profilereview", - "version": "2.1.0" - }, - { - "name": "silinternational/simplesamlphp-module-silauth", - "version": "7.1.1" - }, - { - "name": "silinternational/simplesamlphp-module-sildisco", - "version": "4.0.0" - }, { "name": "silinternational/ssp-utilities", "version": "1.1.0" @@ -393,7 +365,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0" + "version": "v1.29.0" }, { "name": "symfony/polyfill-php81", @@ -437,7 +409,7 @@ }, { "name": "yiisoft/yii2", - "version": "2.0.48.1" + "version": "2.0.49.3" }, { "name": "yiisoft/yii2-composer", diff --git a/local.env.dist b/local.env.dist index 9f8fd47b..fc8485c6 100644 --- a/local.env.dist +++ b/local.env.dist @@ -1,10 +1,10 @@ # These are Required -ADMIN_EMAIL= ADMIN_PASS= IDP_NAME= SECRET_SALT= # These are Optional +ADMIN_EMAIL= ADMIN_NAME= ADMIN_PROTECT_INDEX_PAGE= ANALYTICS_ID= @@ -56,7 +56,6 @@ HELP_CENTER_URL= SAML20_IDP_ENABLE= SECURE_COOKIE= SHOW_SAML_ERRORS= -THEME_USE= TIMEZONE= XDEBUG_REMOTE_HOST= diff --git a/modules/expirychecker/lib/Auth/Process/ExpiryDate.php b/modules/expirychecker/lib/Auth/Process/ExpiryDate.php index 0275dffb..30322af4 100644 --- a/modules/expirychecker/lib/Auth/Process/ExpiryDate.php +++ b/modules/expirychecker/lib/Auth/Process/ExpiryDate.php @@ -25,16 +25,15 @@ class ExpiryDate extends ProcessingFilter const HAS_SEEN_SPLASH_PAGE = 'has_seen_splash_page'; const SESSION_TYPE = 'expirychecker'; - private $warnDaysBefore = 14; - private $originalUrlParam = 'originalurl'; - private $passwordChangeUrl = null; - private $accountNameAttr = null; - private $employeeIdAttr = 'employeeNumber'; - private $expiryDateAttr = null; - private $dateFormat = 'Y-m-d'; + private int $warnDaysBefore = 14; + private string $originalUrlParam = 'originalurl'; + private string|null $passwordChangeUrl = null; + private string|null $accountNameAttr = null; + private string $employeeIdAttr = 'employeeNumber'; + private string|null $expiryDateAttr = null; + private string $dateFormat = 'Y-m-d'; - /** @var LoggerInterface */ - protected $logger; + protected LoggerInterface $logger; /** * Initialize this filter. @@ -42,7 +41,7 @@ class ExpiryDate extends ProcessingFilter * @param array $config Configuration information about this filter. * @param mixed $reserved For future use. */ - public function __construct($config, $reserved) + public function __construct(array $config, mixed $reserved) { parent::__construct($config, $reserved); @@ -77,7 +76,7 @@ public function __construct($config, $reserved) ]); } - protected function loadValuesFromConfig($config, $attributeRules) + protected function loadValuesFromConfig(array $config, array $attributeRules): void { foreach ($attributeRules as $attribute => $rules) { if (array_key_exists($attribute, $config)) { @@ -99,7 +98,7 @@ protected function loadValuesFromConfig($config, $attributeRules) * @param array $state The state data. * @return mixed The attribute value, or null if not found. */ - protected function getAttribute($attributeName, $state) + protected function getAttribute(string $attributeName, array $state): mixed { $attributeData = $state['Attributes'][$attributeName] ?? null; @@ -118,7 +117,7 @@ protected function getAttribute($attributeName, $state) * expire. * @return int The number of days remaining */ - protected function getDaysLeftBeforeExpiry($expiryTimestamp) + protected function getDaysLeftBeforeExpiry(int $expiryTimestamp): int { $now = time(); $end = $expiryTimestamp; @@ -135,7 +134,7 @@ protected function getDaysLeftBeforeExpiry($expiryTimestamp) * @return int The expiration timestamp. * @throws \Exception */ - protected function getExpiryTimestamp($expiryDateAttr, $state) + protected function getExpiryTimestamp(string $expiryDateAttr, array $state): int { $expiryDateString = $this->getAttribute($expiryDateAttr, $state); @@ -153,7 +152,7 @@ protected function getExpiryTimestamp($expiryDateAttr, $state) return $expiryTimestamp; } - public static function hasSeenSplashPageRecently() + public static function hasSeenSplashPageRecently(): bool { $session = Session::getSessionFromRequest(); return (bool)$session->getData( @@ -162,7 +161,7 @@ public static function hasSeenSplashPageRecently() ); } - public static function skipSplashPagesFor($seconds) + public static function skipSplashPagesFor(int $seconds): void { $session = Session::getSessionFromRequest(); $session->setData( @@ -174,7 +173,7 @@ public static function skipSplashPagesFor($seconds) $session->save(); } - protected function initLogger($config) + protected function initLogger(array $config): void { $loggerClass = $config['loggerClass'] ?? Psr3SamlLogger::class; $this->logger = new $loggerClass(); @@ -193,7 +192,7 @@ protected function initLogger($config) * @param int $timestamp The timestamp to check. * @return bool */ - public function isDateInPast(int $timestamp) + public function isDateInPast(int $timestamp): bool { return ($timestamp < time()); } @@ -205,7 +204,7 @@ public function isDateInPast(int $timestamp) * will expire. * @return bool */ - public function isExpired(int $expiryTimestamp) + public function isExpired(int $expiryTimestamp): bool { return $this->isDateInPast($expiryTimestamp); } @@ -219,7 +218,7 @@ public function isExpired(int $expiryTimestamp) * warn the user. * @return boolean */ - public function isTimeToWarn($expiryTimestamp, $warnDaysBefore) + public function isTimeToWarn(int $expiryTimestamp, int $warnDaysBefore): bool { $daysLeft = $this->getDaysLeftBeforeExpiry($expiryTimestamp); return ($daysLeft <= $warnDaysBefore); @@ -235,12 +234,13 @@ public function isTimeToWarn($expiryTimestamp, $warnDaysBefore) * @param int $expiryTimestamp The timestamp when the password will expire. */ public function redirect2PasswordChange( - &$state, - $accountName, - $passwordChangeUrl, - $change_pwd_session, - $expiryTimestamp - ) { + array &$state, + string $accountName, + string $passwordChangeUrl, + string $change_pwd_session, + int $expiryTimestamp + ): void + { $sessionType = 'expirychecker'; /* Save state and redirect. */ $state['expiresAtTimestamp'] = $expiryTimestamp; @@ -302,7 +302,7 @@ public function redirect2PasswordChange( * * @param array &$state The current state. */ - public function process(&$state) + public function process(&$state): void { $employeeId = $this->getAttribute($this->employeeIdAttr, $state); @@ -351,7 +351,7 @@ public function process(&$state) * @param string $accountName The name of the user account. * @param int $expiryTimestamp When the password expired. */ - public function redirectToExpiredPage(&$state, $accountName, $expiryTimestamp) + public function redirectToExpiredPage(array &$state, string $accountName, int $expiryTimestamp): void { assert('is_array($state)'); @@ -379,7 +379,7 @@ public function redirectToExpiredPage(&$state, $accountName, $expiryTimestamp) * @param string $accountName The name of the user account. * @param int $expiryTimestamp When the password will expire. */ - protected function redirectToWarningPage(&$state, $accountName, $expiryTimestamp) + protected function redirectToWarningPage(array &$state, string $accountName, int $expiryTimestamp): void { assert('is_array($state)'); @@ -389,7 +389,7 @@ protected function redirectToWarningPage(&$state, $accountName, $expiryTimestamp ])); $daysLeft = $this->getDaysLeftBeforeExpiry($expiryTimestamp); - $state['daysLeft'] = $daysLeft; + $state['daysLeft'] = (string)$daysLeft; if (isset($state['isPassive']) && $state['isPassive'] === true) { /* We have a passive request. Skip the warning. */ diff --git a/modules/expirychecker/lib/Utilities.php b/modules/expirychecker/lib/Utilities.php index 0e2b80b6..e7ca7e8b 100644 --- a/modules/expirychecker/lib/Utilities.php +++ b/modules/expirychecker/lib/Utilities.php @@ -10,8 +10,8 @@ class Utilities { * * Returns a string with the domain portion of the url (e.g. www.insitehome.org) */ - public static function getUrlDomain($in_url, $start_marker='//', - $end_marker='/') { + public static function getUrlDomain(string $in_url, string $start_marker='//', string $end_marker='/'): string + { $sm_len = strlen($start_marker); $em_len = strlen($end_marker); @@ -29,9 +29,10 @@ public static function getUrlDomain($in_url, $start_marker='//', * * Returns 1 if the domains of the two urls are the same and 0 otherwise. */ - public static function haveSameDomain($url1, $start_marker1, - $end_marker1, $url2, $start_marker2='//', - $end_marker2='/') { + public static function haveSameDomain(string $url1, string $start_marker1, + string $end_marker1, string $url2, string $start_marker2='//', + string $end_marker2='/'): int + { $domain1 = self::getUrlDomain($url1, $start_marker1, $end_marker1); $domain2 = self::getUrlDomain($url2, $start_marker2, $end_marker2); @@ -51,8 +52,9 @@ public static function haveSameDomain($url1, $start_marker1, * for apex to use. If the domains of the change password url and the * original url are different, it appends the StateId to the output. */ - public static function convertOriginalUrl($passwordChangeUrl, - $originalUrlParam, $originalUrl, $stateId ) { + public static function convertOriginalUrl(string $passwordChangeUrl, + string $originalUrlParam, string $originalUrl, string $stateId): string + { $sameDomain = self::haveSameDomain($passwordChangeUrl, '//', '/', $originalUrl, '//', '/'); $original = $originalUrlParam . ":" . urlencode($originalUrl); @@ -80,7 +82,8 @@ public static function convertOriginalUrl($passwordChangeUrl, * @param string $relayState * @return string **/ - public static function getUrlFromRelayState($relayState) { + public static function getUrlFromRelayState(string $relayState): string + { if (strpos($relayState, "http") === 0) { return $relayState; } diff --git a/modules/expirychecker/lib/Validator.php b/modules/expirychecker/lib/Validator.php index ba0020ac..f17be246 100644 --- a/modules/expirychecker/lib/Validator.php +++ b/modules/expirychecker/lib/Validator.php @@ -20,7 +20,7 @@ class Validator * @param LoggerInterface $logger The logger. * @throws Exception */ - public static function validate($value, $rules, $logger, $attribute) + public static function validate(mixed $value, array $rules, LoggerInterface $logger, string $attribute): void { foreach ($rules as $rule) { if ( ! self::isValid($value, $rule, $logger)) { @@ -47,7 +47,7 @@ public static function validate($value, $rules, $logger, $attribute) * @return bool * @throws InvalidArgumentException */ - protected static function isValid($value, $rule, $logger) + protected static function isValid(mixed $value, string $rule, LoggerInterface $logger): bool { switch ($rule) { case self::INT: diff --git a/modules/expirychecker/templates/about2expire.php b/modules/expirychecker/templates/about2expire.php deleted file mode 100644 index 468faeaf..00000000 --- a/modules/expirychecker/templates/about2expire.php +++ /dev/null @@ -1,48 +0,0 @@ -data['header'] = sprintf( - 'Your password will expire in %s %s', - $this->data['daysLeft'], - $this->data['dayOrDays'] -); -$this->data['autofocus'] = 'yesbutton'; - -$this->includeAtTemplateBase('includes/header.php'); - -$dateString = msgfmt_format_message( - $this->getLanguage(), - '{0,date,long}', - [$this->data['expiresAtTimestamp']] -); - -?> -

- The password for your data['accountName']); ?> - account will expire on . -

-

- Would you like to update your password now? -

- -
- - data['formData'] as $name => $value): ?> - - - - - - -
-includeAtTemplateBase('includes/footer.php'); diff --git a/modules/expirychecker/templates/expired.php b/modules/expirychecker/templates/expired.php deleted file mode 100644 index 8b46d306..00000000 --- a/modules/expirychecker/templates/expired.php +++ /dev/null @@ -1,38 +0,0 @@ -data['header'] = 'Your password has expired'; - -$this->includeAtTemplateBase('includes/header.php'); - -$dateString = msgfmt_format_message( - $this->getLanguage(), - '{0,date,long}', - [$this->data['expiresAtTimestamp']] -); - -?> -

- The password for your data['accountName']); ?> - account expired on . -

-

- You will need to update your password before you can continue to where you - were going. -

-

-

- - data['formData'] as $name => $value): ?> - - - - -
-includeAtTemplateBase('includes/footer.php'); diff --git a/modules/material/themes/material/common-announcement.php b/modules/material/themes/material/common-announcement.php index 74d5649b..0fe9dcef 100644 --- a/modules/material/themes/material/common-announcement.php +++ b/modules/material/themes/material/common-announcement.php @@ -1,15 +1,11 @@ data['announcement'])) { ?>
- + data['announcement'] ?>
+ diff --git a/modules/material/themes/material/core/loginuserpass.php b/modules/material/themes/material/core/loginuserpass.php index dd950c80..d355272a 100644 --- a/modules/material/themes/material/core/loginuserpass.php +++ b/modules/material/themes/material/core/loginuserpass.php @@ -19,7 +19,7 @@ function preventDefault(event) { data['recaptcha.siteKey'] ?? null); + $siteKey = htmlentities($this->data['recaptcha.siteKey']); if (! empty($siteKey)) { ?> diff --git a/modules/material/themes/material/expirychecker/about2expire.php b/modules/material/themes/material/expirychecker/about2expire.php index 6ef67783..11500f97 100644 --- a/modules/material/themes/material/expirychecker/about2expire.php +++ b/modules/material/themes/material/expirychecker/about2expire.php @@ -27,7 +27,7 @@

data['daysLeft'] ?? 0; + $daysLeft = $this->data['daysLeft'] ?? '0'; $expiringMessage = $daysLeft < 2 ? $this->t('{material:about2expire:expiring_in_a_day}') : $this->t('{material:about2expire:expiring_soon}', diff --git a/modules/material/themes/material/mfa/low-on-backup-codes.php b/modules/material/themes/material/mfa/low-on-backup-codes.php index 9f148a1c..b8d75d41 100644 --- a/modules/material/themes/material/mfa/low-on-backup-codes.php +++ b/modules/material/themes/material/mfa/low-on-backup-codes.php @@ -29,7 +29,7 @@

- t('{material:mfa:running_out_info}', ['{numBackupCodesRemaining}' => (string)(int)$this->data['numBackupCodesRemaining']]) ?> + t('{material:mfa:running_out_info}', ['{numBackupCodesRemaining}' => $this->data['numBackupCodesRemaining']]) ?>

diff --git a/modules/material/themes/material/mfa/other_mfas.php b/modules/material/themes/material/mfa/other_mfas.php index 199336ca..7d466a59 100644 --- a/modules/material/themes/material/mfa/other_mfas.php +++ b/modules/material/themes/material/mfa/other_mfas.php @@ -1,23 +1,6 @@ data['mfaOptions']; -$currentMfaId = filter_input(INPUT_GET, 'mfaId'); - -function excludeSelf($others, $selfId) { - return array_filter($others, function($option) use ($selfId) { - return $option['id'] != $selfId; - }); -} - -$otherOptions = excludeSelf($mfaOptions, $currentMfaId); - -if (! empty($this->data['managerEmail'])) { - $otherOptions[] = [ - 'type' => 'manager', - 'callback' => '/module.php/mfa/send-manager-mfa.php?StateId='.htmlentities($this->data['stateId']) - ]; -} +$otherOptions = $this->data['otherOptions']; if (count($otherOptions) > 0) { ?>
@@ -29,23 +12,16 @@ function excludeSelf($others, $selfId) {
    data['stateId']).'&mfaId='.htmlentities($option['id']); - - $image = 'mfa-' . $type . '.svg'; - $altText = $this->t('{material:mfa:' . $type . '_icon}'); ?> -
  • +
  • - <?= $altText ?> - - - t('{material:mfa:use_' . $label . '}') ?> + <?= $this->t('{material:mfa:' . $option['type'] . '_icon}') ?> + t('{material:mfa:use_' . $option['label'] . '}') ?>
  • - - - <?= $this->t('{material:mfa:title}') ?> - - - - - - - - -data['supportsU2f']; ?> - - -
    -
    -
    - - t('{material:mfa:header}') ?> - -
    -
    - -
    -
    -
    -
    - <?= $this->t('{material:mfa:u2f_icon}') ?> -
    - -
    -

    - t('{material:mfa:u2f_header}') ?> -

    -
    - - -
    -

    - t('{material:mfa:u2f_instructions}') ?> -

    -
    - -
    -

    - t('{material:mfa:u2f_unsupported}') ?> -

    -
    - - - data['errorMessage']; - if (! empty($message)) { - ?> - - -
    -

    - error - - - - -

    -
    - -
    - - - - -
    - - -
    - -
    - -
    -
    -
    -
    - - diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php index 7f8ccbe2..02fa6ff4 100644 --- a/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php +++ b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php @@ -5,11 +5,7 @@ - - + - - -
    -

    USB Security Key

    - data['supportsWebAuthn']): ?> -

    Please insert your security key and press its button.

    -

    - - -
    - - - -

    - -

    - USB Security Keys are not supported in your current browser. - Please consider a more secure browser like - Google Chrome. -

    - - - data['mfaOptions']) > 1): ?> -

    - Don't have your security key handy? You may also use: -

    -
      - data['mfaOptions'] as $mfaOpt) { - if ($mfaOpt['type'] != 'webauthn') { - ?> -
    • - -
    - - data['managerEmail'])): ?> -

    - Can't use any of your 2-Step Verification options? - - Click here for assistance. -

    - -
    -includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/send-manager-mfa.php b/modules/mfa/templates/send-manager-mfa.php deleted file mode 100644 index 4a274ac6..00000000 --- a/modules/mfa/templates/send-manager-mfa.php +++ /dev/null @@ -1,20 +0,0 @@ -data['header'] = 'Send manager backup code'; -$this->includeAtTemplateBase('includes/header.php'); - -?> -

    - You can send a backup code to your manager to serve as an - additional 2-Step Verification option. - The email address on file (masked for privacy) is data['managerEmail'] ?> -

    -
    - - -
    -includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/www/prompt-for-mfa.php b/modules/mfa/www/prompt-for-mfa.php index 3e63050a..a736fd69 100644 --- a/modules/mfa/www/prompt-for-mfa.php +++ b/modules/mfa/www/prompt-for-mfa.php @@ -67,6 +67,7 @@ // If the user has submitted their MFA value... if (filter_has_var(INPUT_POST, 'submitMfa')) { + /* @var string|array $mfaSubmission */ $mfaSubmission = filter_input(INPUT_POST, 'mfaSubmission'); if (substr($mfaSubmission, 0, 1) == '{') { $mfaSubmission = json_decode($mfaSubmission, true); @@ -96,6 +97,25 @@ $globalConfig = Configuration::getInstance(); +$otherOptions = array_filter($mfaOptions, function($option) use ($mfaId) { + return $option['id'] != $mfaId; +}); +if (! empty($state['managerEmail'])) { + $otherOptions[] = [ + 'type' => 'manager', + 'callback' => '/module.php/mfa/send-manager-mfa.php?StateId='.htmlentities($stateId) + ]; +} +foreach ($otherOptions as &$option) { + $option['callback'] = $option['callback'] ?? sprintf( + '/module.php/mfa/prompt-for-mfa.php?StateId=%s&mfaId=%s', + htmlentities($stateId), + htmlentities($option['id']) + ); + $option['image'] = 'mfa-' . $option['type'] . '.svg'; + $option['label'] = empty($option['id']) ? 'help' : $option['type']; +} + $mfaTemplateToUse = Mfa::getTemplateFor($mfaOption['type']); $t = new Template($globalConfig, $mfaTemplateToUse); @@ -104,7 +124,10 @@ $t->data['mfaOptions'] = $mfaOptions; $t->data['stateId'] = $stateId; $t->data['supportsWebAuthn'] = LoginBrowser::supportsWebAuthn($userAgent); +$browserJsHash = md5_file(__DIR__ . '/simplewebauthn/browser.js'); +$t->data['browserJsPath'] = '/module.php/mfa/simplewebauthn/browser.js?v=' . $browserJsHash; $t->data['managerEmail'] = $state['managerEmail']; +$t->data['otherOptions'] = $otherOptions; $t->show(); $logger->info(json_encode([ diff --git a/modules/mfa/www/send-manager-mfa.php b/modules/mfa/www/send-manager-mfa.php index 97ae0257..cf510162 100644 --- a/modules/mfa/www/send-manager-mfa.php +++ b/modules/mfa/www/send-manager-mfa.php @@ -24,7 +24,7 @@ $logger = LoggerFactory::getAccordingToState($state); if (filter_has_var(INPUT_POST, 'send')) { - Mfa::sendManagerCode($state, $logger); + $errorMessage = Mfa::sendManagerCode($state, $logger); } elseif (filter_has_var(INPUT_POST, 'cancel')) { $moduleUrl = SimpleSAML\Module::getModuleURL('mfa/prompt-for-mfa.php', [ 'StateId' => $stateId, @@ -37,6 +37,7 @@ $t = new Template($globalConfig, 'mfa:send-manager-mfa.php'); $t->data['stateId'] = $stateId; $t->data['managerEmail'] = $state['managerEmail']; +$t->data['errorMessage'] = $errorMessage ?? null; $t->show(); $logger->info(json_encode([ diff --git a/modules/profilereview/lib/Assert.php b/modules/profilereview/lib/Assert.php index f20dff99..af7bf7dc 100644 --- a/modules/profilereview/lib/Assert.php +++ b/modules/profilereview/lib/Assert.php @@ -13,7 +13,7 @@ class Assert * @param string $className The name of the class in question. * @throws InvalidArgumentException */ - public static function classExists(string $className) + public static function classExists(string $className): void { if (! class_exists($className)) { throw new InvalidArgumentException(sprintf( @@ -30,7 +30,7 @@ public static function classExists(string $className) * @param mixed $value The value in question. * @return string */ - protected static function describe($value) + protected static function describe(mixed $value): string { return is_object($value) ? get_class($value) : var_export($value, true); } @@ -42,7 +42,7 @@ protected static function describe($value) * @param string $className The name/classpath of the class in question. * @throws InvalidArgumentException */ - public static function isInstanceOf($object, string $className) + public static function isInstanceOf(mixed $object, string $className): void { if (! ($object instanceof $className)) { throw new InvalidArgumentException(sprintf( diff --git a/modules/profilereview/lib/Auth/Process/ProfileReview.php b/modules/profilereview/lib/Auth/Process/ProfileReview.php index 8b8584a9..789f6838 100644 --- a/modules/profilereview/lib/Auth/Process/ProfileReview.php +++ b/modules/profilereview/lib/Auth/Process/ProfileReview.php @@ -25,15 +25,13 @@ class ProfileReview extends ProcessingFilter const MFA_ADD_PAGE = 'nag-for-mfa.php'; const METHOD_ADD_PAGE = 'nag-for-method.php'; - private $employeeIdAttr = null; - private $mfaLearnMoreUrl = null; - private $profileUrl = null; + private string|null $employeeIdAttr = null; + private string|null $mfaLearnMoreUrl = null; + private string|null $profileUrl = null; - /** @var LoggerInterface */ - protected $logger; + protected LoggerInterface $logger; - /** @var string */ - protected $loggerClass; + protected string $loggerClass; /** * Initialize this filter. @@ -42,7 +40,7 @@ class ProfileReview extends ProcessingFilter * @param mixed $reserved For future use. * @throws \Exception */ - public function __construct($config, $reserved) + public function __construct(array $config, mixed $reserved) { parent::__construct($config, $reserved); $this->initComposerAutoloader(); @@ -65,7 +63,7 @@ public function __construct($config, $reserved) * @param $attributes * @throws \Exception */ - protected function loadValuesFromConfig($config, $attributes) + protected function loadValuesFromConfig(array $config, array $attributes): void { foreach ($attributes as $attribute) { $this->$attribute = $config[$attribute] ?? null; diff --git a/modules/profilereview/lib/LoggerFactory.php b/modules/profilereview/lib/LoggerFactory.php index 83e335d1..8381476a 100644 --- a/modules/profilereview/lib/LoggerFactory.php +++ b/modules/profilereview/lib/LoggerFactory.php @@ -14,7 +14,7 @@ class LoggerFactory * * @throws InvalidArgumentException */ - public static function get($loggerClass) + public static function get(string $loggerClass): LoggerInterface { Assert::classExists($loggerClass); $logger = new $loggerClass(); @@ -32,7 +32,7 @@ public static function get($loggerClass) * * @throws InvalidArgumentException */ - public static function getAccordingToState($state) + public static function getAccordingToState(array $state): LoggerInterface { return self::get($state['loggerClass'] ?? Psr3SamlLogger::class); } diff --git a/modules/profilereview/templates/nag-for-method.php b/modules/profilereview/templates/nag-for-method.php deleted file mode 100644 index bd1c66ef..00000000 --- a/modules/profilereview/templates/nag-for-method.php +++ /dev/null @@ -1,21 +0,0 @@ -data['header'] = 'Set up Recovery Methods'; -$this->includeAtTemplateBase('includes/header.php'); -?> -

    - Did you know you can provide alternate email addresses for password recovery? -

    -

    - We highly encourage you to do this to ensure continuous access and improved security. -

    -
    - - - -
    -includeAtTemplateBase('includes/footer.php'); diff --git a/modules/profilereview/templates/nag-for-mfa.php b/modules/profilereview/templates/nag-for-mfa.php deleted file mode 100644 index 1e31541e..00000000 --- a/modules/profilereview/templates/nag-for-mfa.php +++ /dev/null @@ -1,28 +0,0 @@ -data['header'] = 'Set up 2-Step Verification'; -$this->includeAtTemplateBase('includes/header.php'); - -$mfaLearnMoreUrl = $this->data['mfaLearnMoreUrl']; -?> -

    - Did you know you could greatly increase the security of your account by enabling 2-Step Verification? -

    -

    - We highly encourage you to do this for your own safety. -

    -
    - - - - - -

    Learn more

    - -
    -includeAtTemplateBase('includes/footer.php'); diff --git a/modules/profilereview/templates/review.php b/modules/profilereview/templates/review.php deleted file mode 100644 index 365d908d..00000000 --- a/modules/profilereview/templates/review.php +++ /dev/null @@ -1,62 +0,0 @@ -data['header'] = 'Review 2-Step Verification and Password Recovery'; -$this->includeAtTemplateBase('includes/header.php'); - -$profileUrl = $this->data['profileUrl']; - -?> -

    - Please take a moment to review your 2-Step Verification options and - Password Recovery Methods. -

    -

    - We highly encourage you to do this for your own safety. -

    -

    2-Step Verification

    - - - - - - - - data['mfaOptions'] as $option): ?> - - - - - - - -
    LabelTypeCreatedLast Used
    -

    Password Recovery Methods

    - - - - - - - data['methodOptions'] as $option): ?> - - - - - - -
    EmailVerifiedCreated
    -
    - - - - - -

    Go to Profile

    - -
    -includeAtTemplateBase('includes/footer.php'); diff --git a/modules/profilereview/www/nag.php b/modules/profilereview/www/nag.php index 7c0e91c0..7418418b 100644 --- a/modules/profilereview/www/nag.php +++ b/modules/profilereview/www/nag.php @@ -32,8 +32,8 @@ $t = new Template($globalConfig, 'profilereview:' . $state['template']); $t->data['profileUrl'] = $state['profileUrl']; -$t->data['methodOptions'] = $state['methodOptions']; -$t->data['mfaOptions'] = $state['mfaOptions']; +$t->data['methodOptions'] = $state['methodOptions'] ?? []; +$t->data['mfaOptions'] = $state['mfaOptions'] ?? []; $t->data['mfaLearnMoreUrl'] = $state['mfaLearnMoreUrl']; $t->show(); diff --git a/modules/silauth/lib/Auth/Source/SilAuth.php b/modules/silauth/lib/Auth/Source/SilAuth.php index 6560b4d9..f7c0624f 100644 --- a/modules/silauth/lib/Auth/Source/SilAuth.php +++ b/modules/silauth/lib/Auth/Source/SilAuth.php @@ -23,11 +23,11 @@ */ class SilAuth extends UserPassBase { - protected $authConfig; - protected $idBrokerConfig; - protected $mysqlConfig; - protected $recaptchaConfig; - protected $templateData; + protected array $authConfig; + protected array $idBrokerConfig; + protected array $mysqlConfig; + protected array $recaptchaConfig; + protected array $templateData; /** * Constructor for this authentication source. @@ -38,7 +38,7 @@ class SilAuth extends UserPassBase * @param array $info Information about this authentication source. * @param array $config Configuration for this authentication source. */ - public function __construct($info, $config) + public function __construct(array $info, array $config) { parent::__construct($info, $config); @@ -67,7 +67,7 @@ public function __construct($info, $config) * * @param array &$state Information about the current authentication. */ - public function authenticate(&$state) + public function authenticate(&$state): void { assert('is_array($state)'); @@ -95,7 +95,7 @@ public function authenticate(&$state) assert('FALSE'); } - protected function getTrustedIpAddresses() + protected function getTrustedIpAddresses(): array { $trustedIpAddresses = []; $ipAddressesString = $this->authConfig['trustedIpAddresses'] ?? ''; @@ -108,7 +108,7 @@ protected function getTrustedIpAddresses() return $trustedIpAddresses; } - protected function login($username, $password) + protected function login($username, $password): ?array { $logger = new Psr3StdOutLogger(); $captcha = new Captcha($this->recaptchaConfig['secret'] ?? null); diff --git a/modules/silauth/lib/Auth/Source/auth/AuthError.php b/modules/silauth/lib/Auth/Source/auth/AuthError.php index e46a4abb..6eefe024 100644 --- a/modules/silauth/lib/Auth/Source/auth/AuthError.php +++ b/modules/silauth/lib/Auth/Source/auth/AuthError.php @@ -16,8 +16,8 @@ class AuthError const CODE_RATE_LIMIT_1_MINUTE = 'rate_limit_1_minute'; const CODE_RATE_LIMIT_MINUTES = 'rate_limit_minutes'; - private $code = null; - private $messageParams = []; + private string $code; + private array $messageParams = []; /** * Constructor. @@ -25,7 +25,7 @@ class AuthError * @param string $code One of the AuthError::CODE_* constants. * @param array $messageParams The error message parameters. */ - public function __construct($code, $messageParams = []) + public function __construct(string $code, array $messageParams = []) { $this->code = $code; $this->messageParams = $messageParams; @@ -44,7 +44,7 @@ public function __toString() * * @return string */ - public function getCode() + public function getCode(): string { return $this->code; } @@ -56,7 +56,7 @@ public function getCode() * * @return string Example: '{silauth:error:generic_try_later}' */ - public function getFullSspErrorTag() + public function getFullSspErrorTag(): string { return sprintf( '{%s:%s}', @@ -65,7 +65,7 @@ public function getFullSspErrorTag() ); } - public function getMessageParams() + public function getMessageParams(): array { return $this->messageParams; } diff --git a/modules/silauth/lib/Auth/Source/auth/Authenticator.php b/modules/silauth/lib/Auth/Source/auth/Authenticator.php index 82ca08fa..dc8d10ca 100644 --- a/modules/silauth/lib/Auth/Source/auth/Authenticator.php +++ b/modules/silauth/lib/Auth/Source/auth/Authenticator.php @@ -21,13 +21,9 @@ class Authenticator const BLOCK_AFTER_NTH_FAILED_LOGIN = 50; const MAX_SECONDS_TO_BLOCK = 3600; // 3600 seconds = 1 hour - /** @var AuthError|null */ - private $authError = null; - - /** @var LoggerInterface */ - protected $logger; - - private $userAttributes = null; + private ?AuthError $authError = null; + protected LoggerInterface $logger; + private ?array $userAttributes = null; /** * Attempt to authenticate using the given username and password. Check @@ -41,12 +37,12 @@ class Authenticator * @param LoggerInterface $logger A PSR-3 compliant logger. */ public function __construct( - $username, - $password, - Request $request, - Captcha $captcha, - IdBroker $idBroker, - LoggerInterface $logger + string $username, + string $password, + Request $request, + Captcha $captcha, + IdBroker $idBroker, + LoggerInterface $logger ) { $this->logger = $logger; @@ -143,7 +139,7 @@ public function __construct( * @return int The number of seconds to delay before allowing another such * login attempt. */ - public static function calculateSecondsToDelay($numRecentFailures) + public static function calculateSecondsToDelay(int $numRecentFailures): int { if ( ! self::isEnoughFailedLoginsToBlock($numRecentFailures)) { return 0; @@ -164,7 +160,7 @@ public static function calculateSecondsToDelay($numRecentFailures) * * @return AuthError|null */ - public function getAuthError() + public function getAuthError(): ?AuthError { return $this->authError; } @@ -184,8 +180,9 @@ public function getAuthError() */ public static function getSecondsUntilUnblocked( int $numRecentFailures, - $mostRecentFailureAt - ) { + ?string $mostRecentFailureAt + ): int + { if ($mostRecentFailureAt === null) { return 0; } @@ -216,7 +213,7 @@ public static function getSecondsUntilUnblocked( * * @throws \Exception */ - public function getUserAttributes() + public function getUserAttributes(): ?array { if ($this->userAttributes === null) { throw new \Exception( @@ -242,7 +239,7 @@ public function getUserAttributes() * this request). * @return WaitTime */ - protected function getWaitTimeUntilUnblocked($username, array $ipAddresses) + protected function getWaitTimeUntilUnblocked(string $username, array $ipAddresses): WaitTime { $durationsInSeconds = [ FailedLoginUsername::getSecondsUntilUnblocked($username), @@ -255,7 +252,7 @@ protected function getWaitTimeUntilUnblocked($username, array $ipAddresses) return WaitTime::getLongestWaitTime($durationsInSeconds); } - protected function hasError() + protected function hasError(): bool { return ($this->authError !== null); } @@ -266,46 +263,46 @@ protected function hasError() * * @return bool */ - public function isAuthenticated() + public function isAuthenticated(): bool { return ( ! $this->hasError()); } - protected function isBlockedByRateLimit($username, array $ipAddresses) + protected function isBlockedByRateLimit(string $username, array $ipAddresses): bool { return FailedLoginUsername::isRateLimitBlocking($username) || FailedLoginIpAddress::isRateLimitBlockingAnyOfThese($ipAddresses); } - public static function isCaptchaRequired($username, array $ipAddresses) + public static function isCaptchaRequired(?string $username, array $ipAddresses): bool { return FailedLoginUsername::isCaptchaRequiredFor($username) || FailedLoginIpAddress::isCaptchaRequiredForAnyOfThese($ipAddresses); } - public static function isEnoughFailedLoginsToBlock($numFailedLogins) + public static function isEnoughFailedLoginsToBlock(int $numFailedLogins): bool { return ($numFailedLogins >= self::BLOCK_AFTER_NTH_FAILED_LOGIN); } - public static function isEnoughFailedLoginsToRequireCaptcha($numFailedLogins) + public static function isEnoughFailedLoginsToRequireCaptcha(int $numFailedLogins): bool { return ($numFailedLogins >= self::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN); } - protected function recordFailedLoginBy($username, array $ipAddresses) + protected function recordFailedLoginBy(string $username, array $ipAddresses): void { FailedLoginUsername::recordFailedLoginBy($username, $this->logger); FailedLoginIpAddress::recordFailedLoginBy($ipAddresses, $this->logger); } - protected function resetFailedLoginsBy($username, array $ipAddresses) + protected function resetFailedLoginsBy(string $username, array $ipAddresses): void { FailedLoginUsername::resetFailedLoginsBy($username); FailedLoginIpAddress::resetFailedLoginsBy($ipAddresses); } - protected function setError($code, $messageParams = []) + protected function setError(string $code, array $messageParams = []): void { $this->authError = new AuthError($code, $messageParams); } @@ -313,7 +310,7 @@ protected function setError($code, $messageParams = []) /** * @param WaitTime $waitTime */ - protected function setErrorBlockedByRateLimit($waitTime) + protected function setErrorBlockedByRateLimit(WaitTime $waitTime): void { $unit = $waitTime->getUnit(); $number = $waitTime->getFriendlyNumber(); @@ -331,32 +328,32 @@ protected function setErrorBlockedByRateLimit($waitTime) $this->setError($errorCode, ['{number}' => $number]); } - protected function setErrorGenericTryLater() + protected function setErrorGenericTryLater(): void { $this->setError(AuthError::CODE_GENERIC_TRY_LATER); } - protected function setErrorInvalidLogin() + protected function setErrorInvalidLogin(): void { $this->setError(AuthError::CODE_INVALID_LOGIN); } - protected function setErrorNeedToSetAcctPassword() + protected function setErrorNeedToSetAcctPassword(): void { $this->setError(AuthError::CODE_NEED_TO_SET_ACCT_PASSWORD); } - protected function setErrorPasswordRequired() + protected function setErrorPasswordRequired(): void { $this->setError(AuthError::CODE_PASSWORD_REQUIRED); } - protected function setErrorUsernameRequired() + protected function setErrorUsernameRequired(): void { $this->setError(AuthError::CODE_USERNAME_REQUIRED); } - protected function setUserAttributes($attributes) + protected function setUserAttributes(?array $attributes): void { $this->userAttributes = $attributes; } diff --git a/modules/silauth/lib/Auth/Source/auth/IdBroker.php b/modules/silauth/lib/Auth/Source/auth/IdBroker.php index 78f1cbd4..d542efa5 100644 --- a/modules/silauth/lib/Auth/Source/auth/IdBroker.php +++ b/modules/silauth/lib/Auth/Source/auth/IdBroker.php @@ -3,17 +3,17 @@ use Psr\Log\LoggerInterface; use Sil\Idp\IdBroker\Client\IdBrokerClient; +use Sil\SspBase\Features\fakes\FakeIdBrokerClient; use SimpleSAML\Module\silauth\Auth\Source\saml\User as SamlUser; class IdBroker { - /** @var IdBrokerClient */ - protected $client; + protected IdBrokerClient|FakeIdBrokerClient $client; /** @var LoggerInterface */ - protected $logger; + protected LoggerInterface $logger; - protected $idpDomainName; + protected string $idpDomainName; /** * @@ -64,7 +64,7 @@ public function __construct( * @return array|null The user's attributes (if successful), otherwise null. * @throws \Exception */ - public function getAuthenticatedUser(string $username, string $password) + public function getAuthenticatedUser(string $username, string $password): ?array { $rpOrigin = 'https://' . $this->idpDomainName; $userInfo = $this->client->authenticate($username, $password, $rpOrigin); @@ -102,7 +102,7 @@ public function getAuthenticatedUser(string $username, string $password) * @return string "OK" * @throws Exception */ - public function getSiteStatus() + public function getSiteStatus(): string { return $this->client->getSiteStatus(); } diff --git a/modules/silauth/lib/Auth/Source/captcha/Captcha.php b/modules/silauth/lib/Auth/Source/captcha/Captcha.php index bffc6d46..3d001b8d 100644 --- a/modules/silauth/lib/Auth/Source/captcha/Captcha.php +++ b/modules/silauth/lib/Auth/Source/captcha/Captcha.php @@ -5,14 +5,14 @@ class Captcha { - private $secret; + private ?string $secret; - public function __construct($secret = null) + public function __construct(?string $secret = null) { $this->secret = $secret; } - public function isValidIn(Request $request) + public function isValidIn(Request $request): bool { if (empty($this->secret)) { throw new \RuntimeException('No captcha secret available.', 1487342411); diff --git a/modules/silauth/lib/Auth/Source/config/ConfigManager.php b/modules/silauth/lib/Auth/Source/config/ConfigManager.php index 0e95ecb7..4216b01d 100644 --- a/modules/silauth/lib/Auth/Source/config/ConfigManager.php +++ b/modules/silauth/lib/Auth/Source/config/ConfigManager.php @@ -2,6 +2,7 @@ namespace SimpleSAML\Module\silauth\Auth\Source\config; use SimpleSAML\Module\silauth\Auth\Source\text\Text; +use yii\console\Application; class ConfigManager { @@ -12,7 +13,7 @@ class ConfigManager * * @return array */ - public static function getSspConfig() + public static function getSspConfig(): array { return require __DIR__ . '/ssp-config.php'; } @@ -26,7 +27,7 @@ public static function getSspConfig() * prefix will have been removed, so 'mysql.database' will be returned * as 'database', etc. */ - public static function getSspConfigFor($category) + public static function getSspConfigFor(string $category): array { return self::getConfigFor($category, self::getSspConfig()); } @@ -41,7 +42,7 @@ public static function getSspConfigFor($category) * prefix will have been removed, so 'mysql.database' will be returned * as 'database', etc. */ - public static function getConfigFor($category, $config) + public static function getConfigFor(string $category, array $config): array { $categoryPrefix = $category . self::SEPARATOR; $categoryConfig = []; @@ -60,7 +61,7 @@ public static function getConfigFor($category, $config) * @param array $customConfig * @return array */ - public static function getMergedYii2Config($customConfig) + public static function getMergedYii2Config(array $customConfig): array { $defaultConfig = require __DIR__ . '/yii2-config.php'; return array_replace_recursive( @@ -69,21 +70,21 @@ public static function getMergedYii2Config($customConfig) ); } - private static function initializeYiiClass() + private static function initializeYiiClass(): void { if ( ! class_exists('Yii')) { require_once __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'; } } - public static function getYii2ConsoleApp($customConfig) + public static function getYii2ConsoleApp(array $customConfig): Application { self::initializeYiiClass(); $mergedYii2Config = self::getMergedYii2Config($customConfig); - return new \yii\console\Application($mergedYii2Config); + return new Application($mergedYii2Config); } - public static function initializeYii2WebApp($customConfig = []) + public static function initializeYii2WebApp(array $customConfig = []): void { self::initializeYiiClass(); @@ -99,7 +100,7 @@ public static function initializeYii2WebApp($customConfig = []) $app->log->getLogger(); } - public static function removeCategory($key) + public static function removeCategory(?string $key): bool|string|null { if ($key === null) { return null; diff --git a/modules/silauth/lib/Auth/Source/config/yii2-config.php b/modules/silauth/lib/Auth/Source/config/yii2-config.php index b3f361c5..32a42f1b 100644 --- a/modules/silauth/lib/Auth/Source/config/yii2-config.php +++ b/modules/silauth/lib/Auth/Source/config/yii2-config.php @@ -8,6 +8,7 @@ 'id' => 'SilAuth', 'aliases' => [ '@SimpleSAML/Module/silauth/Auth/Source' => __DIR__ . '/..', + '@Sil/SilAuth' => __DIR__ . '/../../../..', ], 'bootstrap' => [ 'gii', @@ -57,7 +58,7 @@ 'migrate' => [ 'class' => 'yii\console\controllers\MigrateController', 'migrationNamespaces' => [ - 'SimpleSAML\\Module\\silauth\\Auth\\Source\\migrations\\', + 'Sil\\SilAuth\\migrations\\', ], // Disable non-namespaced migrations. diff --git a/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php b/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php index b913d51f..966b36ed 100644 --- a/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php +++ b/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php @@ -9,9 +9,9 @@ */ class CsrfProtector { - protected $csrfSessionKey = 'silauth.csrfToken'; - protected $csrfTokenDataType = 'string'; - private $session; + protected string $csrfSessionKey = 'silauth.csrfToken'; + protected string $csrfTokenDataType = 'string'; + private Session $session; /** * Constructor. @@ -23,13 +23,13 @@ public function __construct(Session $session) $this->session = $session; } - public function changeMasterToken() + public function changeMasterToken(): void { $newMasterToken = $this->generateToken(); $this->setTokenInSession($newMasterToken); } - protected function generateToken() + protected function generateToken(): string { return bin2hex(random_bytes(32)); } @@ -40,7 +40,7 @@ protected function generateToken() * * @return string The master (aka. authoritative) CSRF token. */ - public function getMasterToken() + public function getMasterToken(): string { $masterToken = $this->getTokenFromSession(); if (empty($masterToken)) { @@ -50,7 +50,7 @@ public function getMasterToken() return $masterToken; } - protected function getTokenFromSession() + protected function getTokenFromSession(): mixed { return $this->session->getData( $this->csrfTokenDataType, @@ -65,12 +65,12 @@ protected function getTokenFromSession() * HTTP request. * @return bool */ - public function isTokenCorrect($submittedToken) + public function isTokenCorrect(string $submittedToken): bool { return hash_equals($this->getMasterToken(), $submittedToken); } - protected function setTokenInSession($masterToken) + protected function setTokenInSession(string $masterToken): void { $this->session->setData( $this->csrfTokenDataType, diff --git a/modules/silauth/lib/Auth/Source/http/Request.php b/modules/silauth/lib/Auth/Source/http/Request.php index fe6f242c..fda2d155 100644 --- a/modules/silauth/lib/Auth/Source/http/Request.php +++ b/modules/silauth/lib/Auth/Source/http/Request.php @@ -12,14 +12,14 @@ class Request * * @var IP[] */ - private $trustedIpAddresses = []; + private array $trustedIpAddresses = []; /** * The list of trusted IP address ranges (aka. blocks). * * @var IPBlock[] */ - private $trustedIpAddressRanges = []; + private array $trustedIpAddressRanges = []; /** * Constructor. @@ -39,7 +39,7 @@ public function __construct(array $ipAddressesToTrust = []) } } - public function getCaptchaResponse() + public function getCaptchaResponse(): string { return self::sanitizeInputString(INPUT_POST, 'g-recaptcha-response'); } @@ -53,7 +53,7 @@ public function getCaptchaResponse() * * @return string[] A list of IP addresses. */ - public function getIpAddresses() + public function getIpAddresses(): array { $ipAddresses = []; @@ -84,7 +84,7 @@ public function getIpAddresses() * * @return string|null An IP address, or null if none was available. */ - public function getMostLikelyIpAddress() + public function getMostLikelyIpAddress(): ?string { $untrustedIpAddresses = $this->getUntrustedIpAddresses(); @@ -117,13 +117,13 @@ public function getMostLikelyIpAddress() * @param string $variableName Example: 'username' * @return string */ - public static function getRawInputString(int $inputType, string $variableName) + public static function getRawInputString(int $inputType, string $variableName): string { $input = filter_input($inputType, $variableName); return is_string($input) ? $input : ''; } - public function getUntrustedIpAddresses() + public function getUntrustedIpAddresses(): array { $untrustedIpAddresses = []; foreach ($this->getIpAddresses() as $ipAddress) { @@ -139,7 +139,7 @@ public function getUntrustedIpAddresses() * * @return string The UA string, or an empty string if not found. */ - public static function getUserAgent() + public static function getUserAgent(): string { return self::sanitizeInputString(INPUT_SERVER, 'HTTP_USER_AGENT'); } @@ -151,7 +151,7 @@ public static function getUserAgent() * @param string $ipAddress The IP address in question. * @return bool */ - public function isTrustedIpAddress($ipAddress) + public function isTrustedIpAddress(string $ipAddress): bool { foreach ($this->trustedIpAddresses as $trustedIp) { if ($trustedIp->numeric() === IP::create($ipAddress)->numeric()) { @@ -174,7 +174,7 @@ public function isTrustedIpAddress($ipAddress) * @param string $ipAddress The IP address in question. * @return bool */ - public static function isValidIpAddress($ipAddress) + public static function isValidIpAddress(string $ipAddress): bool { $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; return (filter_var($ipAddress, FILTER_VALIDATE_IP, $flags) !== false); @@ -188,12 +188,12 @@ public static function isValidIpAddress($ipAddress) * @param string $variableName Example: 'username' * @return string */ - public static function sanitizeInputString(int $inputType, string $variableName) + public static function sanitizeInputString(int $inputType, string $variableName): string { return Text::sanitizeString(filter_input($inputType, $variableName)); } - public function trustIpAddress($ipAddress) + public function trustIpAddress(string $ipAddress): void { if ( ! self::isValidIpAddress($ipAddress)) { throw new \InvalidArgumentException(sprintf( @@ -204,7 +204,7 @@ public function trustIpAddress($ipAddress) $this->trustedIpAddresses[] = IP::create($ipAddress); } - public function trustIpAddressRange($ipAddressRangeString) + public function trustIpAddressRange(string $ipAddressRangeString): void { $ipBlock = IPBlock::create($ipAddressRangeString); $this->trustedIpAddressRanges[] = $ipBlock; diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php index ce192668..eefb2573 100644 --- a/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php @@ -18,7 +18,7 @@ class FailedLoginIpAddress extends FailedLoginIpAddressBase implements LoggerAwa /** * @inheritdoc */ - public function attributeLabels() + public function attributeLabels(): array { return ArrayHelper::merge(parent::attributeLabels(), [ 'ip_address' => Yii::t('app', 'IP Address'), @@ -26,7 +26,7 @@ public function attributeLabels() ]); } - public function behaviors() + public function behaviors(): array { return [ [ @@ -38,16 +38,20 @@ public function behaviors() ]; } - public static function countRecentFailedLoginsFor($ipAddress) + public static function countRecentFailedLoginsFor(string $ipAddress): int { - return self::find()->where([ + $count = self::find()->where([ 'ip_address' => strtolower($ipAddress), ])->andWhere([ '>=', 'occurred_at_utc', UtcTime::format('-60 minutes') ])->count(); + if (!is_numeric($count)) { + throw new \Exception('expected a numeric value for recent failed logins by IP address, got '. $count); + } + return (int)$count; } - public static function getFailedLoginsFor($ipAddress) + public static function getFailedLoginsFor(string $ipAddress): array { if ( ! Request::isValidIpAddress($ipAddress)) { throw new \InvalidArgumentException(sprintf( @@ -66,7 +70,7 @@ public static function getFailedLoginsFor($ipAddress) * @param string $ipAddress The IP address. * @return FailedLoginIpAddress|null */ - public static function getMostRecentFailedLoginFor($ipAddress) + public static function getMostRecentFailedLoginFor(string $ipAddress): ?FailedLoginIpAddress { return self::find()->where([ 'ip_address' => strtolower($ipAddress), @@ -83,7 +87,7 @@ public static function getMostRecentFailedLoginFor($ipAddress) * @param string $ipAddress The IP address in question * @return int The number of seconds */ - public static function getSecondsUntilUnblocked($ipAddress) + public static function getSecondsUntilUnblocked(string $ipAddress): int { $failedLogin = self::getMostRecentFailedLoginFor($ipAddress); @@ -93,20 +97,20 @@ public static function getSecondsUntilUnblocked($ipAddress) ); } - public function init() + public function init(): void { $this->initializeLogger(); parent::init(); } - public static function isCaptchaRequiredFor($ipAddress) + public static function isCaptchaRequiredFor(string $ipAddress): bool { return Authenticator::isEnoughFailedLoginsToRequireCaptcha( self::countRecentFailedLoginsFor($ipAddress) ); } - public static function isCaptchaRequiredForAnyOfThese(array $ipAddresses) + public static function isCaptchaRequiredForAnyOfThese(array $ipAddresses): bool { foreach ($ipAddresses as $ipAddress) { if (self::isCaptchaRequiredFor($ipAddress)) { @@ -116,13 +120,13 @@ public static function isCaptchaRequiredForAnyOfThese(array $ipAddresses) return false; } - public static function isRateLimitBlocking($ipAddress) + public static function isRateLimitBlocking(string $ipAddress): bool { $secondsUntilUnblocked = self::getSecondsUntilUnblocked($ipAddress); return ($secondsUntilUnblocked > 0); } - public static function isRateLimitBlockingAnyOfThese($ipAddresses) + public static function isRateLimitBlockingAnyOfThese(array $ipAddresses): bool { foreach ($ipAddresses as $ipAddress) { if (self::isRateLimitBlocking($ipAddress)) { @@ -135,7 +139,8 @@ public static function isRateLimitBlockingAnyOfThese($ipAddresses) public static function recordFailedLoginBy( array $ipAddresses, LoggerInterface $logger - ) { + ): void + { foreach ($ipAddresses as $ipAddress) { $newRecord = new FailedLoginIpAddress(['ip_address' => strtolower($ipAddress)]); @@ -150,7 +155,7 @@ public static function recordFailedLoginBy( } } - public static function resetFailedLoginsBy(array $ipAddresses) + public static function resetFailedLoginsBy(array $ipAddresses): void { foreach ($ipAddresses as $ipAddress) { self::deleteAll(['ip_address' => strtolower($ipAddress)]); diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php index 0675e2e0..25f2b5b1 100644 --- a/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php @@ -16,7 +16,7 @@ class FailedLoginIpAddressBase extends \yii\db\ActiveRecord /** * @inheritdoc */ - public static function tableName() + public static function tableName(): string { return 'failed_login_ip_address'; } @@ -24,7 +24,7 @@ public static function tableName() /** * @inheritdoc */ - public function rules() + public function rules(): array { return [ [['ip_address', 'occurred_at_utc'], 'required'], @@ -36,7 +36,7 @@ public function rules() /** * @inheritdoc */ - public function attributeLabels() + public function attributeLabels(): array { return [ 'id' => Yii::t('app', 'ID'), diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php b/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php index 771132fc..771e1076 100644 --- a/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php @@ -17,14 +17,14 @@ class FailedLoginUsername extends FailedLoginUsernameBase implements LoggerAware /** * @inheritdoc */ - public function attributeLabels() + public function attributeLabels(): array { return ArrayHelper::merge(parent::attributeLabels(), [ 'occurred_at_utc' => Yii::t('app', 'Occurred At (UTC)'), ]); } - public function behaviors() + public function behaviors(): array { return [ [ @@ -36,13 +36,17 @@ public function behaviors() ]; } - public static function countRecentFailedLoginsFor($username) + public static function countRecentFailedLoginsFor(string $username): int { - return self::find()->where([ + $count = self::find()->where([ 'username' => strtolower($username), ])->andWhere([ '>=', 'occurred_at_utc', UtcTime::format('-60 minutes') ])->count(); + if (!is_numeric($count)) { + throw new \Exception('expected a numeric value for recent failed logins by username, got '. $count); + } + return (int)$count; } /** @@ -51,7 +55,7 @@ public static function countRecentFailedLoginsFor($username) * @param string $username The username. * @return FailedLoginUsername[] An array of any matching records. */ - public static function getFailedLoginsFor($username) + public static function getFailedLoginsFor(string $username): array { return self::findAll(['username' => strtolower($username)]); } @@ -63,7 +67,7 @@ public static function getFailedLoginsFor($username) * @param string $username The username. * @return FailedLoginUsername|null */ - public static function getMostRecentFailedLoginFor($username) + public static function getMostRecentFailedLoginFor(string $username): ?FailedLoginUsername { return self::find()->where([ 'username' => strtolower($username), @@ -80,7 +84,7 @@ public static function getMostRecentFailedLoginFor($username) * @param string $username The username in question * @return int The number of seconds */ - public static function getSecondsUntilUnblocked($username) + public static function getSecondsUntilUnblocked(string $username): int { $failedLogin = self::getMostRecentFailedLoginFor($username); @@ -90,7 +94,7 @@ public static function getSecondsUntilUnblocked($username) ); } - public function init() + public function init(): void { $this->initializeLogger(); parent::init(); @@ -102,13 +106,13 @@ public function init() * @param string $username The username * @return bool */ - public static function isRateLimitBlocking($username) + public static function isRateLimitBlocking(string $username): bool { $secondsUntilUnblocked = self::getSecondsUntilUnblocked($username); return ($secondsUntilUnblocked > 0); } - public static function isCaptchaRequiredFor($username) + public static function isCaptchaRequiredFor(?string $username): bool { if (empty($username)) { return false; @@ -119,9 +123,10 @@ public static function isCaptchaRequiredFor($username) } public static function recordFailedLoginBy( - $username, + string $username, LoggerInterface $logger - ) { + ): void + { $newRecord = new FailedLoginUsername(['username' => strtolower($username)]); if ( ! $newRecord->save()) { $logger->critical(json_encode([ @@ -133,7 +138,7 @@ public static function recordFailedLoginBy( } } - public static function resetFailedLoginsBy($username) + public static function resetFailedLoginsBy(string $username): void { self::deleteAll(['username' => strtolower($username)]); } diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php b/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php index a774ed47..a3c32cae 100644 --- a/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php @@ -16,7 +16,7 @@ class FailedLoginUsernameBase extends \yii\db\ActiveRecord /** * @inheritdoc */ - public static function tableName() + public static function tableName(): string { return 'failed_login_username'; } @@ -24,7 +24,7 @@ public static function tableName() /** * @inheritdoc */ - public function rules() + public function rules(): array { return [ [['username', 'occurred_at_utc'], 'required'], @@ -36,7 +36,7 @@ public function rules() /** * @inheritdoc */ - public function attributeLabels() + public function attributeLabels(): array { return [ 'id' => Yii::t('app', 'ID'), diff --git a/modules/silauth/lib/Auth/Source/saml/User.php b/modules/silauth/lib/Auth/Source/saml/User.php index 5986f5d1..864fe118 100644 --- a/modules/silauth/lib/Auth/Source/saml/User.php +++ b/modules/silauth/lib/Auth/Source/saml/User.php @@ -11,13 +11,14 @@ public static function convertToSamlFieldNames( string $email, string $uuid, string $idpDomainName, - $passwordExpirationDate, + ?string $passwordExpirationDate, array $mfa, array $method, - $managerEmail, - $profileReview, + ?string $managerEmail, + string $profileReview, array $member - ) { + ): array + { // eduPersonUniqueId (only alphanumeric allowed) $alphaNumericUuid = str_replace('-', '', $uuid); diff --git a/modules/silauth/lib/Auth/Source/system/System.php b/modules/silauth/lib/Auth/Source/system/System.php index ea576186..cfe11950 100644 --- a/modules/silauth/lib/Auth/Source/system/System.php +++ b/modules/silauth/lib/Auth/Source/system/System.php @@ -11,19 +11,19 @@ class System { - protected $logger; + protected LoggerInterface|NullLogger $logger; /** * Constructor. * * @param LoggerInterface|null $logger (Optional:) A PSR-3 compatible logger. */ - public function __construct($logger = null) + public function __construct(LoggerInterface $logger = null) { $this->logger = $logger ?? new NullLogger(); } - protected function isDatabaseOkay() + protected function isDatabaseOkay(): bool { try { FailedLoginIpAddress::getMostRecentFailedLoginFor(''); @@ -34,7 +34,7 @@ protected function isDatabaseOkay() } } - protected function isRequiredConfigPresent() + protected function isRequiredConfigPresent(): bool { $globalConfig = Configuration::getInstance(); @@ -57,7 +57,7 @@ protected function isRequiredConfigPresent() * * @throws \Exception */ - public function reportStatus() + public function reportStatus(): void { if ( ! $this->isRequiredConfigPresent()) { $this->reportError('Config problem', 1485984755); @@ -75,7 +75,7 @@ public function reportStatus() * * @param string $message The error message. */ - protected function logError($message) + protected function logError(string $message): void { $this->logger->error($message); } @@ -88,7 +88,7 @@ protected function logError($message) * @param int $code An error code. * @throws \Exception */ - protected function reportError($message, $code) + protected function reportError(string $message, int $code): void { $this->logError($message); throw new \Exception($message, $code); diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php index a2a04fc7..aee14acd 100644 --- a/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php @@ -5,7 +5,7 @@ class FakeFailedIdBroker extends FakeIdBroker { - public function getAuthenticatedUser(string $username, string $password) + public function getAuthenticatedUser(string $username, string $password): ?array { $this->logger->info('FAKE FAILURE: rejecting {username} and {password}.', [ 'username' => var_export($username, true), @@ -14,7 +14,7 @@ public function getAuthenticatedUser(string $username, string $password) return parent::getAuthenticatedUser($username, $password); } - protected function getDesiredResponse() + protected function getDesiredResponse(): Response { return new Response(400); } diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php index 8e54e8f7..a22c85c1 100644 --- a/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php @@ -5,13 +5,13 @@ class FakeInvalidIdBroker extends FakeIdBroker { - public function getAuthenticatedUser(string $username, string $password) + public function getAuthenticatedUser(string $username, string $password): ?array { $this->logger->info('FAKE ERROR: invalid/unexpected response.'); return parent::getAuthenticatedUser($username, $password); } - protected function getDesiredResponse() + protected function getDesiredResponse(): Response { return new Response(404); } diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php index 21d36b25..d385fa11 100644 --- a/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php @@ -5,7 +5,7 @@ class FakeSuccessfulIdBroker extends FakeIdBroker { - public function getAuthenticatedUser(string $username, string $password) + public function getAuthenticatedUser(string $username, string $password): ?array { $this->logger->info('FAKE SUCCESS: accepting {username} and {password}.', [ 'username' => var_export($username, true), @@ -14,7 +14,7 @@ public function getAuthenticatedUser(string $username, string $password) return parent::getAuthenticatedUser($username, $password); } - protected function getDesiredResponse() + protected function getDesiredResponse(): Response { return new Response(200, [], json_encode([ 'uuid' => '11111111-aaaa-1111-aaaa-111111111111', diff --git a/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php b/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php index b6d5387c..9ddb58e6 100644 --- a/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php +++ b/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php @@ -6,7 +6,7 @@ class DummyFailedCaptcha extends Captcha { - public function isValidIn(Request $request) + public function isValidIn(Request $request): bool { return false; } diff --git a/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummySuccessfulCaptcha.php b/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummySuccessfulCaptcha.php index 7a68b3d4..1bdfded2 100644 --- a/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummySuccessfulCaptcha.php +++ b/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummySuccessfulCaptcha.php @@ -6,7 +6,7 @@ class DummySuccessfulCaptcha extends Captcha { - public function isValidIn(Request $request) + public function isValidIn(Request $request): bool { return true; } diff --git a/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php b/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php index ae2cfbf7..f2334110 100644 --- a/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php +++ b/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php @@ -12,12 +12,12 @@ class DummyRequest extends Request * * @return string[] A list containing the dummy IP address. */ - public function getIpAddresses() + public function getIpAddresses(): array { return [$this->dummyIpAddress]; } - public function setDummyIpAddress($dummyIpAddress) + public function setDummyIpAddress(string $dummyIpAddress): void { if ( ! self::isValidIpAddress($dummyIpAddress)) { throw new \InvalidArgumentException(sprintf( diff --git a/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php index 84f2163d..8b1c3daf 100644 --- a/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php +++ b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php @@ -9,7 +9,7 @@ class FailedLoginIpAddressTest extends TestCase { - protected function setDbFixture($recordsData) + protected function setDbFixture(array $recordsData): void { FailedLoginIpAddress::deleteAll(); foreach ($recordsData as $recordData) { diff --git a/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php index bd403eff..b5b571c2 100644 --- a/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php +++ b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php @@ -9,7 +9,7 @@ class FailedLoginUsernameTest extends TestCase { - protected function setDbFixture($recordsData) + protected function setDbFixture(array $recordsData): void { FailedLoginUsername::deleteAll(); foreach ($recordsData as $recordData) { diff --git a/modules/silauth/lib/Auth/Source/text/Text.php b/modules/silauth/lib/Auth/Source/text/Text.php index eed1282c..6649ca65 100644 --- a/modules/silauth/lib/Auth/Source/text/Text.php +++ b/modules/silauth/lib/Auth/Source/text/Text.php @@ -10,7 +10,7 @@ class Text * @param string|mixed $input The input. * @return string The sanitized string. */ - public static function sanitizeString($input) + public static function sanitizeString(mixed $input): string { $inputAsString = is_string($input) ? $input : ''; $output = filter_var($inputAsString, FILTER_SANITIZE_STRING, [ @@ -26,7 +26,7 @@ public static function sanitizeString($input) * @param string $needle The string to search for. * @return boolean */ - public static function startsWith(string $haystack, string $needle) + public static function startsWith(string $haystack, string $needle): bool { $length = mb_strlen($needle); return (mb_substr($haystack, 0, $length) === $needle); diff --git a/modules/silauth/lib/Auth/Source/time/UtcTime.php b/modules/silauth/lib/Auth/Source/time/UtcTime.php index af727882..e269c14e 100644 --- a/modules/silauth/lib/Auth/Source/time/UtcTime.php +++ b/modules/silauth/lib/Auth/Source/time/UtcTime.php @@ -39,7 +39,7 @@ public function __toString() * @throws Exception If an invalid date/time string is provided, an * \Exception will be thrown. */ - public static function format(string $dateTimeString = 'now') + public static function format(string $dateTimeString = 'now'): string { return (string)(new UtcTime($dateTimeString)); } @@ -55,7 +55,7 @@ public static function format(string $dateTimeString = 'now') * passed. * @return int The number of seconds remaining. */ - public static function getRemainingSeconds(int $totalSeconds, int $elapsedSeconds) + public static function getRemainingSeconds(int $totalSeconds, int $elapsedSeconds): int { $remainingSeconds = $totalSeconds - $elapsedSeconds; return max($remainingSeconds, 0); @@ -71,7 +71,7 @@ public static function getRemainingSeconds(int $totalSeconds, int $elapsedSecond * (presumably in the past, though not necessarily). * @return int The number of seconds */ - public function getSecondsSince(UtcTime $otherUtcTime) + public function getSecondsSince(UtcTime $otherUtcTime): int { return $this->getTimestamp() - $otherUtcTime->getTimestamp(); } @@ -85,7 +85,7 @@ public function getSecondsSince(UtcTime $otherUtcTime) * \Exception will be thrown. * @throws \InvalidArgumentException */ - public static function getSecondsSinceDateTime(string $dateTimeString) + public static function getSecondsSinceDateTime(string $dateTimeString): int { if (empty($dateTimeString)) { throw new \InvalidArgumentException(sprintf( @@ -98,12 +98,12 @@ public static function getSecondsSinceDateTime(string $dateTimeString) return $nowUtc->getSecondsSince($dateTimeUtc); } - public function getSecondsUntil(UtcTime $otherUtcTime) + public function getSecondsUntil(UtcTime $otherUtcTime): int { return $otherUtcTime->getTimestamp() - $this->getTimestamp(); } - public function getTimestamp() + public function getTimestamp(): int { return $this->dateTime->getTimestamp(); } @@ -113,7 +113,7 @@ public function getTimestamp() * * @return string */ - public static function now() + public static function now(): string { return self::format('now'); } diff --git a/modules/silauth/lib/Auth/Source/time/WaitTime.php b/modules/silauth/lib/Auth/Source/time/WaitTime.php index ba5094f3..216b46ba 100644 --- a/modules/silauth/lib/Auth/Source/time/WaitTime.php +++ b/modules/silauth/lib/Auth/Source/time/WaitTime.php @@ -11,8 +11,8 @@ class WaitTime const UNIT_MINUTE = 'minute'; const UNIT_SECOND = 'second'; - private $friendlyNumber = null; - private $unit = null; + private int $friendlyNumber; + private string $unit; /** * Constructor. @@ -22,7 +22,7 @@ class WaitTime * * @param int $secondsToWait The number of seconds the user must wait. */ - public function __construct($secondsToWait) + public function __construct(int $secondsToWait) { if ($secondsToWait <= 5) { $this->friendlyNumber = 5; @@ -36,7 +36,7 @@ public function __construct($secondsToWait) } } - public function getFriendlyNumber() + public function getFriendlyNumber(): int { return $this->friendlyNumber; } @@ -48,7 +48,7 @@ public function getFriendlyNumber() * seconds. * @return WaitTime */ - public static function getLongestWaitTime(array $durationsInSeconds) + public static function getLongestWaitTime(array $durationsInSeconds): WaitTime { if (empty($durationsInSeconds)) { throw new \InvalidArgumentException('No durations given.', 1487605801); @@ -56,7 +56,7 @@ public static function getLongestWaitTime(array $durationsInSeconds) return new WaitTime(max($durationsInSeconds)); } - public function getUnit() + public function getUnit(): string { return $this->unit; } diff --git a/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php b/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php index 65fabe5a..c07c72c8 100644 --- a/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php +++ b/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php @@ -7,9 +7,9 @@ trait LoggerAwareTrait { /** @var LoggerInterface */ - protected $logger; + protected LoggerInterface $logger; - public function initializeLogger() + public function initializeLogger(): void { if (empty($this->logger)) { $this->logger = new NullLogger(); @@ -22,7 +22,7 @@ public function initializeLogger() * @param LoggerInterface $logger A PSR-3 compliant logger. * @return null */ - public function setLogger(LoggerInterface $logger) + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; } diff --git a/modules/silauth/lib/Auth/Source/migrations/M161213135750CreateInitialTables.php b/modules/silauth/migrations/M161213135750CreateInitialTables.php similarity index 94% rename from modules/silauth/lib/Auth/Source/migrations/M161213135750CreateInitialTables.php rename to modules/silauth/migrations/M161213135750CreateInitialTables.php index bc0dab3d..a123a8f6 100644 --- a/modules/silauth/lib/Auth/Source/migrations/M161213135750CreateInitialTables.php +++ b/modules/silauth/migrations/M161213135750CreateInitialTables.php @@ -1,12 +1,12 @@ createTable('{{user}}', [ 'id' => 'pk', @@ -45,7 +45,7 @@ public function safeUp() ); } - public function safeDown() + public function safeDown(): void { $this->dropForeignKey( 'fk_prev_pw_user_user_id', diff --git a/modules/silauth/lib/Auth/Source/migrations/M161213150831SwitchToUtcForDateTimes.php b/modules/silauth/migrations/M161213150831SwitchToUtcForDateTimes.php similarity index 82% rename from modules/silauth/lib/Auth/Source/migrations/M161213150831SwitchToUtcForDateTimes.php rename to modules/silauth/migrations/M161213150831SwitchToUtcForDateTimes.php index c9d38f96..145a496a 100644 --- a/modules/silauth/lib/Auth/Source/migrations/M161213150831SwitchToUtcForDateTimes.php +++ b/modules/silauth/migrations/M161213150831SwitchToUtcForDateTimes.php @@ -1,19 +1,19 @@ renameColumn('{{user}}', 'block_until', 'block_until_utc'); $this->renameColumn('{{user}}', 'last_updated', 'last_updated_utc'); $this->renameColumn('{{previous_password}}', 'created', 'created_utc'); } - public function safeDown() + public function safeDown(): void { $this->renameColumn('{{previous_password}}', 'created_utc', 'created'); $this->renameColumn('{{user}}', 'last_updated_utc', 'last_updated'); diff --git a/modules/silauth/lib/Auth/Source/migrations/M170214141109CreateFailedLoginsTable.php b/modules/silauth/migrations/M170214141109CreateFailedLoginsTable.php similarity index 90% rename from modules/silauth/lib/Auth/Source/migrations/M170214141109CreateFailedLoginsTable.php rename to modules/silauth/migrations/M170214141109CreateFailedLoginsTable.php index acba2e8a..a43abc01 100644 --- a/modules/silauth/lib/Auth/Source/migrations/M170214141109CreateFailedLoginsTable.php +++ b/modules/silauth/migrations/M170214141109CreateFailedLoginsTable.php @@ -1,12 +1,12 @@ dropIndex('idx_failed_logins_ip_address', '{{failed_logins}}'); $this->dropIndex('idx_failed_logins_username', '{{failed_logins}}'); diff --git a/modules/silauth/lib/Auth/Source/migrations/M170214145629RemoveOldTables.php b/modules/silauth/migrations/M170214145629RemoveOldTables.php similarity index 84% rename from modules/silauth/lib/Auth/Source/migrations/M170214145629RemoveOldTables.php rename to modules/silauth/migrations/M170214145629RemoveOldTables.php index 0b407f9d..24031976 100644 --- a/modules/silauth/lib/Auth/Source/migrations/M170214145629RemoveOldTables.php +++ b/modules/silauth/migrations/M170214145629RemoveOldTables.php @@ -1,12 +1,12 @@ dropForeignKey( 'fk_prev_pw_user_user_id', @@ -21,7 +21,7 @@ public function safeUp() $this->dropTable('{{user}}'); } - public function safeDown() + public function safeDown(): bool { echo "M170214145629RemoveOldTables cannot be reverted.\n"; diff --git a/modules/silauth/lib/Auth/Source/migrations/M170215141724SplitFailedLoginsTable.php b/modules/silauth/migrations/M170215141724SplitFailedLoginsTable.php similarity index 91% rename from modules/silauth/lib/Auth/Source/migrations/M170215141724SplitFailedLoginsTable.php rename to modules/silauth/migrations/M170215141724SplitFailedLoginsTable.php index 305e21fe..e71ebef5 100644 --- a/modules/silauth/lib/Auth/Source/migrations/M170215141724SplitFailedLoginsTable.php +++ b/modules/silauth/migrations/M170215141724SplitFailedLoginsTable.php @@ -1,12 +1,12 @@ dropIndex('idx_failed_logins_ip_address', '{{failed_logins}}'); @@ -36,7 +36,7 @@ public function safeUp() ); } - public function safeDown() + public function safeDown(): bool { echo "M170215141724SplitFailedLoginsTable cannot be reverted.\n"; return false; diff --git a/modules/silauth/www/loginuserpass.php b/modules/silauth/www/loginuserpass.php index 5a34a285..dcddce3a 100644 --- a/modules/silauth/www/loginuserpass.php +++ b/modules/silauth/www/loginuserpass.php @@ -1,6 +1,7 @@ data['csrfToken'] = $csrfProtector->getMasterToken(); $t->data['profileUrl'] = $state['templateData']['profileUrl'] ?? ''; $t->data['helpCenterUrl'] = $state['templateData']['helpCenterUrl'] ?? ''; +$t->data['announcement'] = AnnouncementUtils::getAnnouncement(); /* For simplicity's sake, don't bother telling this Request to trust any IP * addresses. This is okay because we only track the failures of untrusted @@ -96,6 +98,8 @@ $request = new Request(); if (Authenticator::isCaptchaRequired($username, $request->getUntrustedIpAddresses())) { $t->data['recaptcha.siteKey'] = $recaptchaSiteKey; +} else { + $t->data['recaptcha.siteKey'] = null; } if (isset($state['SPMetadata'])) { diff --git a/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php b/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php index 0f816211..e3c8408e 100644 --- a/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php +++ b/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php @@ -2,6 +2,7 @@ namespace SimpleSAML\Module\sildisco\Auth\Process; +use SAML2\XML\saml\NameID; use Sil\SspUtils\Metadata; /** @@ -39,7 +40,7 @@ class AddIdp2NameId extends \SimpleSAML\Auth\ProcessingFilter { * * @var string|bool */ - private $nameQualifier; + private string|bool $nameQualifier; /** @@ -51,7 +52,7 @@ class AddIdp2NameId extends \SimpleSAML\Auth\ProcessingFilter { * * @var string|bool */ - private $spNameQualifier; + private sring|bool $spNameQualifier; /** @@ -61,7 +62,7 @@ class AddIdp2NameId extends \SimpleSAML\Auth\ProcessingFilter { * * @var string */ - protected $format; + protected ?string $format; /** @@ -70,7 +71,7 @@ class AddIdp2NameId extends \SimpleSAML\Auth\ProcessingFilter { * @param array $config Configuration information about this filter. * @param mixed $reserved For future use. */ - public function __construct($config, $reserved) { + public function __construct(array $config, mixed $reserved) { parent::__construct($config, $reserved); assert('is_array($config)'); @@ -93,12 +94,13 @@ public function __construct($config, $reserved) { } /** - * @param $nameId \SAML2\XML\saml\NameID + * @param $nameId NameID * @param $IDPNamespace string * * Modifies the nameID object by adding text to the end of its value attribute */ - public function appendIdp($nameId, $IDPNamespace) { + public function appendIdp(NameID $nameId, string $IDPNamespace): void + { $suffix = self::DELIMITER . $IDPNamespace; $value = $nameId->getValue(); @@ -112,7 +114,8 @@ public function appendIdp($nameId, $IDPNamespace) { * * @param array &$state The current state array */ - public function process(&$state) { + public function process(&$state): void + { assert('is_array($state)'); $samlIDP = $state[self::IDP_KEY]; diff --git a/modules/sildisco/lib/Auth/Process/LogUser.php b/modules/sildisco/lib/Auth/Process/LogUser.php index c1cc2a55..835ed287 100644 --- a/modules/sildisco/lib/Auth/Process/LogUser.php +++ b/modules/sildisco/lib/Auth/Process/LogUser.php @@ -3,6 +3,7 @@ namespace SimpleSAML\Module\sildisco\Auth\Process; use Aws\DynamoDb\Marshaler; +use Aws\Sdk; /** * This Auth Proc logs information about each successful login to an AWS Dynamodb table. @@ -38,13 +39,13 @@ class LogUser extends \SimpleSAML\Auth\ProcessingFilter // The host of the aws dynamodb - private $dynamoEndpoint; + private ?string $dynamoEndpoint; // The region of the aws dynamodb - private $dynamoRegion; + private ?string $dynamoRegion; // The name of the aws dynamodb table that stores the login data - private $dynamoLogTable; + private ?string $dynamoLogTable; /** * Initialize this filter, parse configuration. @@ -52,9 +53,9 @@ class LogUser extends \SimpleSAML\Auth\ProcessingFilter * @param array $config Configuration information about this filter. * @param mixed $reserved For future use. */ - public function __construct($config, $reserved) { + public function __construct(array $config, mixed $reserved) + { parent::__construct($config, $reserved); - assert(is_array($config)); $this->dynamoEndpoint = $config[self::DYNAMO_ENDPOINT_KEY] ?? null; $this->dynamoRegion = $config[self::DYNAMO_REGION_KEY] ?? null; @@ -62,11 +63,12 @@ public function __construct($config, $reserved) { } /** - * Log info for a user's login to Dyanmodb + * Log info for a user's login to Dynamodb * * @param array &$state The current state array */ - public function process(&$state) { + public function process(&$state): void + { if (! $this->configsAreValid()) { return; } @@ -103,7 +105,7 @@ public function process(&$state) { $sdkConfig['endpoint'] = $this->dynamoEndpoint; } - $sdk = new \Aws\Sdk($sdkConfig); + $sdk = new Sdk($sdkConfig); $dynamodb = $sdk->createDynamoDb(); $marshaler = new Marshaler(); @@ -137,7 +139,8 @@ public function process(&$state) { } } - private function configsAreValid() { + private function configsAreValid(): bool + { $msg = ' config value not provided to LogUser.'; if (empty($this->dynamoRegion)) { @@ -153,7 +156,8 @@ private function configsAreValid() { return true; } - private function getIdp(&$state) { + private function getIdp(array &$state) + { if (empty($state[self::IDP_KEY])) { return 'No IDP available'; } @@ -182,7 +186,8 @@ private function getIdp(&$state) { } // Get the current user's common name attribute and/or eduPersonPrincipalName and/or employeeNumber - private function getUserAttributes($state) { + private function getUserAttributes(array $state): array + { $attributes = $state['Attributes']; $cn = $this->getAttributeFrom($attributes, 'urn:oid:2.5.4.3', 'cn'); @@ -208,7 +213,8 @@ private function getUserAttributes($state) { return $userAttrs; } - private function getAttributeFrom($attributes, $oidKey, $friendlyKey) { + private function getAttributeFrom(array $attributes, string $oidKey, string $friendlyKey): string + { if (!empty($attributes[$oidKey])) { return $attributes[$oidKey][0]; } @@ -222,7 +228,7 @@ private function getAttributeFrom($attributes, $oidKey, $friendlyKey) { // Dynamodb seems to complain when a value is an empty string. // This ensures that only attributes with a non empty value get included. - private function addUserAttribute($attributes, $attrKey, $attr) { + private function addUserAttribute(array $attributes, string $attrKey, string $attr): array { if (!empty($attr)) { $attributes[$attrKey] = $attr; } diff --git a/modules/sildisco/lib/Auth/Process/TagGroup.php b/modules/sildisco/lib/Auth/Process/TagGroup.php index 59402907..5d8ccf68 100644 --- a/modules/sildisco/lib/Auth/Process/TagGroup.php +++ b/modules/sildisco/lib/Auth/Process/TagGroup.php @@ -16,7 +16,8 @@ class TagGroup extends \SimpleSAML\Auth\ProcessingFilter { const IDP_CODE_KEY = 'IDPNamespace'; - public function prependIdp2Groups($attributes, $attributeLabel, $idpLabel) { + public function prependIdp2Groups(array $attributes, string $attributeLabel, string $idpLabel): array + { $newGroups = []; $delimiter = '|'; diff --git a/modules/sildisco/lib/Auth/Source/SP.php b/modules/sildisco/lib/Auth/Source/SP.php index b9781797..6cf2863b 100644 --- a/modules/sildisco/lib/Auth/Source/SP.php +++ b/modules/sildisco/lib/Auth/Source/SP.php @@ -2,7 +2,7 @@ /** * Modified from origin: modules/saml/lib/Auth/Source/SP.php - * 2022-09-26 -- Merged with simplesamlphp 1.19.6, lines/sections marked with GTIS are modified + * 2024-06-06 -- Merged with simplesamlphp 1.19.8, lines/sections marked with GTIS are modified */ declare(strict_types=1); @@ -561,6 +561,7 @@ private function startSSO2(Configuration $idpMetadata, array $state): void if (isset($state['saml:Audience'])) { $ar->setAudiences($state['saml:Audience']); } + if (isset($state['ForceAuthn'])) { $ar->setForceAuthn((bool) $state['ForceAuthn']); } @@ -866,7 +867,6 @@ public function reauthenticate(array &$state) { $session = Session::getSessionFromRequest(); $data = $session->getAuthState($this->authId); - $data = $session->getAuthState($this->authId); if ($data === null) { throw new Error\NoState(); } diff --git a/modules/sildisco/lib/IdP/SAML2.php b/modules/sildisco/lib/IdP/SAML2.php index 4d975bd6..9f7f2345 100644 --- a/modules/sildisco/lib/IdP/SAML2.php +++ b/modules/sildisco/lib/IdP/SAML2.php @@ -5,7 +5,7 @@ * Copied from the built-in simplesamlphp module modules/saml/lib/IdP/SAML2.php with code inserted. * See comment below about GTIS. * - * 2022-09-26 -- Merged with simplesamlphp 1.19.6, lines marked with GTIS are modified + * 2024-06-06 -- Merged with simplesamlphp 1.19.8, lines marked with GTIS are modified */ declare(strict_types=1); @@ -487,7 +487,7 @@ public static function receiveAuthnRequest(\SimpleSAML\IdP $idp) * to authenticate through any of the IDP's that have so far * been used for authentication. * - * In order for this for this to avoid forcing authentication + * In order for this to avoid forcing authentication * in every case, the hub's saml20-idp-hosted.php entry needs * to include an authproc entry that adds each authenticating * IDP to a list in the session. @@ -1512,4 +1512,4 @@ private static function buildResponse( return $r; } -} \ No newline at end of file +} diff --git a/modules/sildisco/lib/IdPDisco.php b/modules/sildisco/lib/IdPDisco.php index 0ea08a17..4808a8ff 100644 --- a/modules/sildisco/lib/IdPDisco.php +++ b/modules/sildisco/lib/IdPDisco.php @@ -18,23 +18,23 @@ class IdPDisco extends \SimpleSAML\XHTML\IdPDisco { /* The session type for this class */ - public static $sessionType = 'sildisco:authentication'; + public static string $sessionType = 'sildisco:authentication'; /* The session key for checking if the current user has the beta_tester cookie */ - public static $betaTesterSessionKey = 'beta_tester'; + public static string $betaTesterSessionKey = 'beta_tester'; /* The idp metadata key that says whether an IDP is betaEnabled */ - public static $betaEnabledMdKey = 'betaEnabled'; + public static string $betaEnabledMdKey = 'betaEnabled'; /* The idp metadata key that says whether an IDP is enabled */ - public static $enabledMdKey = 'enabled'; + public static string $enabledMdKey = 'enabled'; /* The sp metadata key that gives the name of the SP */ - public static $spNameMdKey = 'name'; + public static string $spNameMdKey = 'name'; /* Used to get the SP Entity ID, e.g. $spEntityId = $this->session->getData($sessionDataType, $sessionKeyForSP); */ - public static $sessionDataType = 'sildisco:authentication'; - public static $sessionKeyForSP = 'spentityid'; + public static string $sessionDataType = 'sildisco:authentication'; + public static string $sessionKeyForSP = 'spentityid'; /** @@ -44,7 +44,7 @@ class IdPDisco extends \SimpleSAML\XHTML\IdPDisco * * @param string $message The message which should be logged. */ - protected function log($message) + protected function log($message): void { \SimpleSAML\Logger::info('SildiscoIdPDisco.'.$this->instance.': '.$message); } @@ -54,7 +54,7 @@ private function getMetadataPath() { return __DIR__ . '/../../../metadata/'; } - private function getSPEntityIDAndReducedIdpList() + private function getSPEntityIDAndReducedIdpList(): array { $idpList = $this->getIdPList(); @@ -76,7 +76,7 @@ private function getSPEntityIDAndReducedIdpList() * * The IdP disco parameters should be set before calling this function. */ - public function handleRequest() + public function handleRequest(): void { $this->start(); @@ -119,7 +119,7 @@ public function handleRequest() $t->data['entityID'] = $this->spEntityId; $t->data['spName'] = $spName; $t->data['urlpattern'] = htmlspecialchars(\SimpleSAML\Utils\HTTP::getSelfURLNoQuery()); - $t->data['announcement'] = AnnouncementUtils::getSimpleAnnouncement(); + $t->data['announcement'] = AnnouncementUtils::getAnnouncement(); $t->data['helpCenterUrl'] = $this->config->getValue('helpCenterUrl', ''); $t->show(); @@ -127,14 +127,15 @@ public function handleRequest() /** * @param array $idpList the IDPs with their metadata - * @param bool $isBetaTester optional (default=null) just for unit testing + * @param bool|null $isBetaTester optional (default=null) just for unit testing * @return array $idpList * * If the current user has the beta_tester cookie, then for each IDP in * the idpList that has 'betaEnabled' => true, give it 'enabled' => true * */ - public static function enableBetaEnabled($idpList, $isBetaTester=null) { + public static function enableBetaEnabled(array $idpList, ?bool $isBetaTester=null): array + { if ( $isBetaTester === null) { $session = \SimpleSAML\Session::getSessionFromRequest(); @@ -168,7 +169,7 @@ public static function enableBetaEnabled($idpList, $isBetaTester=null) { * * @return string|null The entity id if it is valid, null if not. */ - protected function validateIdP($idp) + protected function validateIdP($idp): ?string { if ($idp === null) { return null; @@ -203,7 +204,6 @@ protected function validateIdP($idp) return null; } - if (array_key_exists($idp, $idpList) && $idpList[$idp]['enabled']) { return $idp; } diff --git a/modules/sildisco/www/disco.php b/modules/sildisco/www/disco.php index 6c3c08f0..e6b87f69 100644 --- a/modules/sildisco/www/disco.php +++ b/modules/sildisco/www/disco.php @@ -1,7 +1,7 @@ handleResponse($state, $issuer, $attributes); -assert(false); \ No newline at end of file +assert(false); diff --git a/modules/sildisco/www/sp/saml2-logout.php b/modules/sildisco/www/sp/saml2-logout.php index 05d9c14b..53219f92 100644 --- a/modules/sildisco/www/sp/saml2-logout.php +++ b/modules/sildisco/www/sp/saml2-logout.php @@ -6,7 +6,7 @@ * This endpoint handles both logout requests and logout responses. * * Similar to modules/saml/www/sp/saml2-logout.php - * 2022-09-26 -- Merged with simplesamlphp 1.19.6, lines marked with GTIS are modified + * 2024-06-06 -- Merged with simplesamlphp 1.19.8, lines marked with GTIS are modified */ if (!array_key_exists('PATH_INFO', $_SERVER)) { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e8abf91e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "simplesamlphp-module-material", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simplesamlphp-module-material", + "dependencies": { + "@simplewebauthn/browser": "^4.1.0" + } + }, + "node_modules/@simplewebauthn/browser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-4.1.0.tgz", + "integrity": "sha512-tIsEfShC1rrqrsNb44tOFuSriAFCz4tkdDnCjHfn2rYxgz+t+yqEvuIRfJHQpFrWSnZPdsjrAHtasj6lzfGI6w==" + } + } +}