From 98d37ed96537d1271b5d31918c8ec1182b5b1666 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 26 Feb 2024 21:22:50 +0100 Subject: [PATCH] First work --- .gitattributes | 14 + .github/dependabot.yml | 27 ++ .github/workflows/autolock-conversations.yml | 25 ++ .github/workflows/documentation.yml | 35 ++ .github/workflows/php.yml | 282 ++++++++++++++++ .markdownlintignore | 1 + .markdownlintrc | 4 + .php_cs.dist | 12 + README.md | 32 +- codecov.yml | 18 + composer.json | 48 +++ phpcs.xml | 17 + phpstan-dev.neon | 4 + phpstan.neon | 4 + phpunit.xml | 19 ++ .../oasis-sstc-saml-schema-assertion-1.1.xsd | 201 +++++++++++ .../oasis-sstc-saml-schema-protocol-1.1.xsd | 132 ++++++++ resources/schemas/xmldsig-core-schema.xsd | 318 ++++++++++++++++++ src/SAML11/Constants.php | 83 +++++ .../Exception/ProtocolViolationException.php | 31 ++ src/SAML11/XML/saml/AbstractActionType.php | 87 +++++ src/SAML11/XML/saml/AbstractAttributeType.php | 98 ++++++ .../AbstractAuthenticationStatementType.php | 150 +++++++++ .../XML/saml/AbstractAuthorityBindingType.php | 110 ++++++ .../XML/saml/AbstractNameIdentifierType.php | 105 ++++++ src/SAML11/XML/saml/AbstractSamlElement.php | 22 ++ src/SAML11/XML/saml/AbstractStatement.php | 123 +++++++ src/SAML11/XML/saml/AbstractStatementType.php | 14 + .../saml/AbstractSubjectConfirmationType.php | 120 +++++++ .../XML/saml/AbstractSubjectLocalityType.php | 108 ++++++ src/SAML11/XML/saml/AbstractSubjectType.php | 101 ++++++ src/SAML11/XML/saml/Action.php | 14 + src/SAML11/XML/saml/AssertionIDReference.php | 76 +++++ src/SAML11/XML/saml/Attribute.php | 14 + .../XML/saml/AttributeDesignatorTypeTrait.php | 34 ++ src/SAML11/XML/saml/AttributeValue.php | 168 +++++++++ src/SAML11/XML/saml/Audience.php | 30 ++ .../XML/saml/AuthenticationStatement.php | 14 + src/SAML11/XML/saml/AuthorityBinding.php | 14 + src/SAML11/XML/saml/ConfirmationMethod.php | 76 +++++ src/SAML11/XML/saml/DecisionTypeEnum.php | 12 + src/SAML11/XML/saml/NameIdentifier.php | 14 + src/SAML11/XML/saml/Subject.php | 14 + src/SAML11/XML/saml/SubjectConfirmation.php | 14 + .../XML/saml/SubjectConfirmationData.php | 168 +++++++++ src/SAML11/XML/saml/SubjectLocality.php | 14 + tests/autoload.php | 18 + tests/resources/xml/saml_Action.xml | 1 + .../xml/saml_AssertionIDReference.xml | 1 + tests/resources/xml/saml_Attribute.xml | 4 + tests/resources/xml/saml_AttributeValue.xml | 1 + tests/resources/xml/saml_Audience.xml | 1 + .../xml/saml_AuthenticationStatement.xml | 19 ++ tests/resources/xml/saml_AuthorityBinding.xml | 1 + .../resources/xml/saml_ConfirmationMethod.xml | 1 + tests/resources/xml/saml_NameIdentifier.xml | 1 + tests/resources/xml/saml_Subject.xml | 16 + .../xml/saml_SubjectConfirmation.xml | 13 + .../xml/saml_SubjectConfirmationData.xml | 1 + tests/resources/xml/saml_SubjectLocality.xml | 1 + tests/src/SAML11/XML/saml/ActionTest.php | 61 ++++ .../XML/saml/AssertionIDReferenceTest.php | 54 +++ tests/src/SAML11/XML/saml/AttributeTest.php | 64 ++++ .../SAML11/XML/saml/AttributeValueTest.php | 141 ++++++++ tests/src/SAML11/XML/saml/AudienceTest.php | 60 ++++ .../XML/saml/AuthenticationStatementTest.php | 177 ++++++++++ .../SAML11/XML/saml/AuthorityBindingTest.php | 61 ++++ .../XML/saml/ConfirmationMethodTest.php | 54 +++ .../SAML11/XML/saml/NameIdentifierTest.php | 63 ++++ .../XML/saml/SubjectConfirmationDataTest.php | 142 ++++++++ .../XML/saml/SubjectConfirmationTest.php | 124 +++++++ .../SAML11/XML/saml/SubjectLocalityTest.php | 71 ++++ tests/src/SAML11/XML/saml/SubjectTest.php | 132 ++++++++ tools/composer-require-checker.json | 4 + tools/linters/.markdown-lint.yml | 4 + tools/linters/.yaml-lint.yml | 7 + 76 files changed, 4317 insertions(+), 2 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/autolock-conversations.yml create mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/php.yml create mode 100644 .markdownlintignore create mode 100644 .markdownlintrc create mode 100644 .php_cs.dist create mode 100644 codecov.yml create mode 100644 composer.json create mode 100644 phpcs.xml create mode 100644 phpstan-dev.neon create mode 100644 phpstan.neon create mode 100644 phpunit.xml create mode 100644 resources/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd create mode 100644 resources/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd create mode 100644 resources/schemas/xmldsig-core-schema.xsd create mode 100644 src/SAML11/Constants.php create mode 100644 src/SAML11/Exception/ProtocolViolationException.php create mode 100644 src/SAML11/XML/saml/AbstractActionType.php create mode 100644 src/SAML11/XML/saml/AbstractAttributeType.php create mode 100644 src/SAML11/XML/saml/AbstractAuthenticationStatementType.php create mode 100644 src/SAML11/XML/saml/AbstractAuthorityBindingType.php create mode 100644 src/SAML11/XML/saml/AbstractNameIdentifierType.php create mode 100644 src/SAML11/XML/saml/AbstractSamlElement.php create mode 100644 src/SAML11/XML/saml/AbstractStatement.php create mode 100644 src/SAML11/XML/saml/AbstractStatementType.php create mode 100644 src/SAML11/XML/saml/AbstractSubjectConfirmationType.php create mode 100644 src/SAML11/XML/saml/AbstractSubjectLocalityType.php create mode 100644 src/SAML11/XML/saml/AbstractSubjectType.php create mode 100644 src/SAML11/XML/saml/Action.php create mode 100644 src/SAML11/XML/saml/AssertionIDReference.php create mode 100644 src/SAML11/XML/saml/Attribute.php create mode 100644 src/SAML11/XML/saml/AttributeDesignatorTypeTrait.php create mode 100644 src/SAML11/XML/saml/AttributeValue.php create mode 100644 src/SAML11/XML/saml/Audience.php create mode 100644 src/SAML11/XML/saml/AuthenticationStatement.php create mode 100644 src/SAML11/XML/saml/AuthorityBinding.php create mode 100644 src/SAML11/XML/saml/ConfirmationMethod.php create mode 100644 src/SAML11/XML/saml/DecisionTypeEnum.php create mode 100644 src/SAML11/XML/saml/NameIdentifier.php create mode 100644 src/SAML11/XML/saml/Subject.php create mode 100644 src/SAML11/XML/saml/SubjectConfirmation.php create mode 100644 src/SAML11/XML/saml/SubjectConfirmationData.php create mode 100644 src/SAML11/XML/saml/SubjectLocality.php create mode 100644 tests/autoload.php create mode 100644 tests/resources/xml/saml_Action.xml create mode 100644 tests/resources/xml/saml_AssertionIDReference.xml create mode 100644 tests/resources/xml/saml_Attribute.xml create mode 100644 tests/resources/xml/saml_AttributeValue.xml create mode 100644 tests/resources/xml/saml_Audience.xml create mode 100644 tests/resources/xml/saml_AuthenticationStatement.xml create mode 100644 tests/resources/xml/saml_AuthorityBinding.xml create mode 100644 tests/resources/xml/saml_ConfirmationMethod.xml create mode 100644 tests/resources/xml/saml_NameIdentifier.xml create mode 100644 tests/resources/xml/saml_Subject.xml create mode 100644 tests/resources/xml/saml_SubjectConfirmation.xml create mode 100644 tests/resources/xml/saml_SubjectConfirmationData.xml create mode 100644 tests/resources/xml/saml_SubjectLocality.xml create mode 100644 tests/src/SAML11/XML/saml/ActionTest.php create mode 100644 tests/src/SAML11/XML/saml/AssertionIDReferenceTest.php create mode 100644 tests/src/SAML11/XML/saml/AttributeTest.php create mode 100644 tests/src/SAML11/XML/saml/AttributeValueTest.php create mode 100644 tests/src/SAML11/XML/saml/AudienceTest.php create mode 100644 tests/src/SAML11/XML/saml/AuthenticationStatementTest.php create mode 100644 tests/src/SAML11/XML/saml/AuthorityBindingTest.php create mode 100644 tests/src/SAML11/XML/saml/ConfirmationMethodTest.php create mode 100644 tests/src/SAML11/XML/saml/NameIdentifierTest.php create mode 100644 tests/src/SAML11/XML/saml/SubjectConfirmationDataTest.php create mode 100644 tests/src/SAML11/XML/saml/SubjectConfirmationTest.php create mode 100644 tests/src/SAML11/XML/saml/SubjectLocalityTest.php create mode 100644 tests/src/SAML11/XML/saml/SubjectTest.php create mode 100644 tools/composer-require-checker.json create mode 100644 tools/linters/.markdown-lint.yml create mode 100644 tools/linters/.yaml-lint.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..38f98ef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +/.phpunit.cache/ export-ignore +/build/ export-ignore +/tests/SAML11/ export-ignore +/tools/ export-ignore +codecov.yml export-ignore +.gitattributes export-ignore +.gitignore export-ignore +phpcs.xml export-ignore +phpunit.xml export-ignore +phpunit-interoperability.xml export-ignore +phpstan.xml export-ignore +phpstan-dev.xml export-ignore +README-DEV.md export-ignore +UPGRADING.md export-ignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0013236 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +--- + +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + groups: + all-actions: + patterns: ["*"] + + - package-ecosystem: "composer" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + groups: + dev-dependencies: + dependency-type: "development" + update-types: + - "minor" + - "patch" diff --git a/.github/workflows/autolock-conversations.yml b/.github/workflows/autolock-conversations.yml new file mode 100644 index 0000000..9d569ce --- /dev/null +++ b/.github/workflows/autolock-conversations.yml @@ -0,0 +1,25 @@ +--- + +name: 'Lock Threads' + +on: # yamllint disable-line rule:truthy + schedule: + - cron: '0 0 1 * *' + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +concurrency: + group: lock + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v5 + with: + issue-inactive-days: '90' + pr-inactive-days: '90' + log-output: true diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..c477bc6 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,35 @@ +--- + +name: Documentation + +on: # yamllint disable-line rule:truthy + push: + branches: [master, release-*] + paths: + - '**.md' + pull_request: + branches: [master, release-*] + paths: + - '**.md' + workflow_dispatch: + +jobs: + quality: + name: Quality checks + runs-on: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Lint markdown files + uses: nosborn/github-action-markdown-cli@v3 + with: + files: . + ignore_path: .markdownlintignore + + - name: Perform spell check + uses: codespell-project/actions-codespell@v2 + with: + path: '**/*.md' + check_filenames: true + ignore_words_list: tekst diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..77c01fe --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,282 @@ +--- + +name: CI + +on: # yamllint disable-line rule:truthy + push: + branches: ['**'] + paths-ignore: + - '**.md' + pull_request: + branches: [master, release-*] + paths-ignore: + - '**.md' + workflow_dispatch: + +jobs: + linter: + name: Linter + runs-on: ['ubuntu-latest'] + + steps: + - uses: actions/checkout@v4 + with: + # super-linter needs the full git history to get the + # list of files that changed across commits + fetch-depth: 0 + + - name: Lint Code Base + uses: super-linter/super-linter/slim@v6 + env: + # To report GitHub Actions status checks + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LINTER_RULES_PATH: 'tools/linters' + LOG_LEVEL: NOTICE + VALIDATE_ALL_CODEBASE: true + VALIDATE_BASH: true + VALIDATE_BASH_EXEC: true + VALIDATE_JSON: true + VALIDATE_PHP_BUILTIN: true + VALIDATE_YAML: true + VALIDATE_GITHUB_ACTIONS: true + + quality: + name: Quality control + runs-on: [ubuntu-latest] + + steps: + - name: Setup PHP, with composer and extensions + id: setup-php + # https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + # Should be the higest supported version, so we can use the newest tools + php-version: '8.3' + tools: composer, composer-require-checker, composer-unused, phpcs, phpstan + extensions: ctype, date, dom, filter, hash, mbstring, openssl, pcre, soap, spl, xml + coverage: none + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - uses: actions/checkout@v4 + + - name: Get composer cache directory + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: $COMPOSER_CACHE + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Check code for hard dependencies missing in composer.json + continue-on-error: true + run: composer-require-checker check --config-file=tools/composer-require-checker.json composer.json + + - name: Check code for unused dependencies in composer.json + run: composer-unused + + - name: PHP Code Sniffer + run: phpcs + + - name: PHPStan + run: | + phpstan analyze -c phpstan.neon + + - name: PHPStan (testsuite) + run: | + phpstan analyze -c phpstan-dev.neon + + security: + name: Security checks + runs-on: [ubuntu-latest] + steps: + - name: Setup PHP, with composer and extensions + # https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + # Should be the lowest supported version + php-version: '8.1' + extensions: ctype, date, dom, filter, hash, mbstring, openssl, pcre, soap, spl, xml + tools: composer + coverage: none + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - uses: actions/checkout@v4 + + - name: Get composer cache directory + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: $COMPOSER_CACHE + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Security check for locked dependencies + run: composer audit + + - name: Update Composer dependencies + run: composer update --no-progress --prefer-dist --optimize-autoloader + + - name: Security check for updated dependencies + run: composer audit + + unit-tests-linux: + name: "Unit tests, PHP ${{ matrix.php-versions }}, ${{ matrix.operating-system }}" + runs-on: ${{ matrix.operating-system }} + needs: [linter, quality, security] + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest] + php-versions: ['8.1', '8.2', '8.3'] + + steps: + - name: Setup PHP, with composer and extensions + # https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: ctype, date, dom, filter, hash, mbstring, openssl, pcre, soap, spl, xml + tools: composer + ini-values: error_reporting=E_ALL + coverage: pcov + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v4 + + - name: Get composer cache directory + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: $COMPOSER_CACHE + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Run unit tests with coverage + if: ${{ matrix.php-versions == '8.3' }} + run: vendor/bin/phpunit + + - name: Run unit tests (no coverage) + if: ${{ matrix.php-versions != '8.3' }} + run: vendor/bin/phpunit --no-coverage + + - name: Save coverage data + if: ${{ matrix.php-versions == '8.3' }} + uses: actions/upload-artifact@v4 + with: + name: coverage-data + path: ${{ github.workspace }}/build + + unit-tests-windows: + name: "Unit tests, PHP ${{ matrix.php-versions }}, ${{ matrix.operating-system }}" + runs-on: ${{ matrix.operating-system }} + needs: [linter, quality, security] + strategy: + fail-fast: true + matrix: + operating-system: [windows-latest] + php-versions: ['8.1', '8.2', '8.3'] + + steps: + - name: Setup PHP, with composer and extensions + # https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: ctype, date, dom, filter, hash, mbstring, openssl, pcre, soap, spl, xml + tools: composer + ini-values: error_reporting=E_ALL + coverage: none + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v4 + + - name: Get composer cache directory + run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$env:GITHUB_ENV" + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: $COMPOSER_CACHE + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Run unit tests + run: vendor/bin/phpunit --no-coverage + + coverage: + name: Code coverage + runs-on: [ubuntu-latest] + needs: [unit-tests-linux] + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: coverage-data + path: ${{ github.workspace }}/build + + - name: Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + verbose: true + + cleanup: + name: Cleanup artifacts + needs: [unit-tests-linux, coverage] + runs-on: [ubuntu-latest] + if: | + always() && + needs.coverage.result == 'success' || + (needs.unit-tests-linux == 'success' && needs.coverage == 'skipped') + + steps: + - uses: geekyeggo/delete-artifact@v4 + with: + name: coverage-data diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..140fada --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +vendor/* diff --git a/.markdownlintrc b/.markdownlintrc new file mode 100644 index 0000000..b077f0e --- /dev/null +++ b/.markdownlintrc @@ -0,0 +1,4 @@ +{ + "default": true, + "MD013": false +} diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..44e6279 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,12 @@ +in([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + ])->setFinder($finder) ; diff --git a/README.md b/README.md index 7e7109c..8168445 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ -# saml11 -SAML 1.1 PHP library for SimpleSAMLphp ADFS-module +# SimpleSAMLphp SAML 1.1 library + +![CI](https://github.com/simplesamlphp/saml11/workflows/CI/badge.svg?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/simplesamlphp/saml11/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/simplesamlphp/saml11/?branch=master) +[![Coverage Status](https://codecov.io/gh/simplesamlphp/saml11/branch/master/graph/badge.svg)](https://codecov.io/gh/simplesamlphp/saml11) +[![Type coverage](https://shepherd.dev/github/simplesamlphp/saml11/coverage.svg)](https://shepherd.dev/github/simplesamlphp/saml11) +[![Psalm Level](https://shepherd.dev/github/simplesamlphp/saml11/level.svg)](https://shepherd.dev/github/simplesamlphp/saml11) + +A PHP library for SAML 1.1 related functionality. + +It is used by the ADFS-module of [SimpleSAMLphp](https://www.simplesamlphp.org). + +## Before you use it + +**DO NOT USE THIS LIBRARY UNLESS YOU ARE INTIMATELY FAMILIAR WITH THE SAML 1.1 SPECIFICATION.** + +If you are not familiar with the SAML 1,1 specification you should probably not use this library. + +## Usage + +* Install with [Composer](https://getcomposer.org/doc/00-intro.md), run the following command in your project: + +```bash +composer require simplesamlphp/saml11:^1.0 +``` + +## License + +This library is licensed under the LGPL license version 2.1. +For more details see [LICENSE](https://raw.github.com/simplesamlphp/saml11/master/LICENSE). diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..623fb22 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,18 @@ +--- + +coverage: + status: + project: + default: + target: 0% + threshold: 2% + patch: false +comment: + layout: "diff" + behavior: once + require_changes: true + require_base: false + require_head: true + branches: null +github_checks: + annotations: false diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2b99164 --- /dev/null +++ b/composer.json @@ -0,0 +1,48 @@ +{ + "name": "simplesamlphp/saml11", + "description": "SAML 1.1 PHP library for SimpleSAMLphp ADFS-module", + "license": "LGPL-2.1-or-later", + "authors": [ + { + "name": "Tim van Dijen", + "email": "tvdijen@gmail.com" + } + ], + "require": { + "php": "^8.1", + "ext-date": "*", + "ext-dom": "*", + "ext-filter": "*", + "ext-pcre": "*", + + "psr/clock": "^1.0", + "simplesamlphp/assert": "^1.0", + "simplesamlphp/xml-common": "^1.14", + "simplesamlphp/xml-security": "^1.6" + }, + "require-dev": { + "beste/clock": "^3.0", + "simplesamlphp/simplesamlphp-test-framework": "^1.5" + }, + "autoload": { + "psr-4": { + "SimpleSAML\\SAML11\\": "src/SAML11" + } + }, + "autoload-dev": { + "psr-4": { + "SimpleSAML\\Test\\SAML11\\": "tests/SAML11/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "v1.0.x-dev" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..5592f2e --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,17 @@ + + + + By default it is less stringent about long lines than other coding standards + + + + + + + src + tests + + + + + diff --git a/phpstan-dev.neon b/phpstan-dev.neon new file mode 100644 index 0000000..09d9773 --- /dev/null +++ b/phpstan-dev.neon @@ -0,0 +1,4 @@ +parameters: + level: 5 + paths: + - tests diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..21cf590 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 1 + paths: + - src diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..dd2981f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,19 @@ + + + + + + + + + + ./tests/SAML11 + + + + + + ./src + + + diff --git a/resources/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd b/resources/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd new file mode 100644 index 0000000..7ffd235 --- /dev/null +++ b/resources/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd @@ -0,0 +1,201 @@ + + + + + + Document identifier: oasis-sstc-saml-schema-assertion-1.1 + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November, 2002): + Initial standard schema. + V1.1 (September, 2003): + * Note that V1.1 of this schema has the same XML namespace as V1.0. + Rebased ID content directly on XML Schema types + Added DoNotCacheCondition element and DoNotCacheConditionType + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd b/resources/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd new file mode 100644 index 0000000..36fbcbc --- /dev/null +++ b/resources/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd @@ -0,0 +1,132 @@ + + + + + + + Document identifier: oasis-sstc-saml-schema-protocol-1.1 + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November, 2002): + Initial standard schema. + V1.1 (September, 2003): + * Note that V1.1 of this schema has the same XML namespace as V1.0. + Rebased ID content directly on XML Schema types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/schemas/xmldsig-core-schema.xsd b/resources/schemas/xmldsig-core-schema.xsd new file mode 100644 index 0000000..e35571b --- /dev/null +++ b/resources/schemas/xmldsig-core-schema.xsd @@ -0,0 +1,318 @@ + + + + + + ]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAML11/Constants.php b/src/SAML11/Constants.php new file mode 100644 index 0000000..820f7cf --- /dev/null +++ b/src/SAML11/Constants.php @@ -0,0 +1,83 @@ +setContent($value); + } + + + /** + * Collect the value of the Namespace-property + * + * @return string|null + */ + public function getNamespace(): ?string + { + return $this->Namespace; + } + + + /** + * Convert XML into an ActionType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $Namespace = self::getOptionalAttribute($xml, 'Namespace'); + + return new static($xml->textContent, $Namespace); + } + + + /** + * Convert this ActionType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this ActionType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + $e->textContent = $this->getContent(); + + if ($this->getNamespace() !== null) { + $e->setAttribute('Namespace', $this->getNamespace()); + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractAttributeType.php b/src/SAML11/XML/saml/AbstractAttributeType.php new file mode 100644 index 0000000..dc01a5d --- /dev/null +++ b/src/SAML11/XML/saml/AbstractAttributeType.php @@ -0,0 +1,98 @@ + $attributeValue + */ + public function __construct( + protected string $AttributeName, + protected string $AttributeNamespace, + protected array $attributeValue, + ) { + Assert::nullOrNotWhitespaceOnly($AttributeName, SchemaViolationException::class); + Assert::nullOrValidURI($AttributeNamespace, SchemaViolationException::class); // Covers the empty string + Assert::allIsInstanceOf($attributeValue, AttributeValue::class, SchemaViolationException::class); + } + + + /** + * Collect the value of the attributeValue-property + * + * @return array<\SimpleSAML\SAML11\XML\saml\AttributeValue> + */ + public function getAttributeValue(): array + { + return $this->attributeValue; + } + + + /** + * Convert XML into an AttributeType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $attributeValue = AttributeValue::getChildrenOfClass($xml); + $AttributeName = self::getOptionalAttribute($xml, 'AttributeName'); + $AttributeNamespace = self::getOptionalAttribute($xml, 'AttributeNamespace'); + + return new static($AttributeName, $AttributeNamespace, $attributeValue); + } + + + /** + * Convert this AttributeType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this AttributeType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + + if ($this->getAttributeName() !== null) { + $e->setAttribute('AttributeName', $this->getAttributeName()); + } + + if ($this->getAttributeNamespace() !== null) { + $e->setAttribute('AttributeNamespace', $this->getAttributeNamespace()); + } + + foreach ($this->getAttributeValue() as $av) { + $av->toXML($e); + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractAuthenticationStatementType.php b/src/SAML11/XML/saml/AbstractAuthenticationStatementType.php new file mode 100644 index 0000000..3a75a07 --- /dev/null +++ b/src/SAML11/XML/saml/AbstractAuthenticationStatementType.php @@ -0,0 +1,150 @@ + $authorityBinding + */ + public function __construct( + protected string $authenticationMethod, + protected DateTimeImmutable $authenticationInstant, + protected ?SubjectLocality $subjectLocality = null, + protected array $authorityBinding = [], + ) { + Assert::validURI($authenticationMethod); + Assert::allIsInstanceOf($authorityBinding, AuthorityBinding::class, SchemaViolationException::class); + + parent::__construct($subject); + } + + + /** + * Collect the value of the authorityBinding-property + * + * @return array<\SimpleSAML\SAML11\XML\saml\AuthorityBinding> + */ + public function getAuthorityBinding(): array + { + return $this->authorityBinding; + } + + + /** + * Collect the value of the subjectLocality-property + * + * @return \SimpleSAML\SAML11\XML\saml\SubjectLocality|null + */ + public function getSubjectLocality(): ?SubjectLocality + { + return $this->subjectLocality; + } + + + /** + * Collect the value of the authenticationMethod-property + * + * @return string + */ + public function getAuthenticationMethod(): string + { + return $this->authenticationMethod; + } + + + /** + * Collect the value of the authenticationInstant-property + * + * @return \DateTimeImmutable + */ + public function getAuthenticationInstant(): DateTimeImmutable + { + return $this->authenticationInstant; + } + + + /** + * Convert XML into an AuthenticationStatementType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $authenticationInstant = self::getAttribute($xml, 'AuthenticationInstant'); + // Strip sub-seconds - See paragraph 1.2.2 of SAML core specifications + $authenticationInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $authenticationInstant, 1); + + Assert::validDateTimeZulu($authenticationInstant, ProtocolViolationException::class); + $authenticationInstant = new DateTimeImmutable($authenticationInstant); + + $authorityBinding = AuthorityBinding::getChildrenOfClass($xml); + $subjectLocality = SubjectLocality::getChildrenOfClass($xml); + Assert::maxCount($subjectLocality, 1, TooManyElementsException::class); + + $subject = Subject::getChildrenOfClass($xml); + Assert::minCount($subject, 1, MissingElementException::class); + Assert::maxCount($subject, 1, TooManyElementsException::class); + + return new static( + self::getAttribute($xml, 'AuthenticationMethod'), + $authenticationInstant, + array_pop($subject), + array_pop($subjectLocality), + $authorityBinding, + ); + } + + + /** + * Convert this AuthenticationStatementType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this AuthenticationStatementType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::toXML($parent); + + $e->setAttribute('AuthenticationMethod', $this->getAuthenticationMethod()); + $e->setAttribute('AuthenticationInstant', $this->getAuthenticationInstant()->format(C::DATETIME_FORMAT)); + + $this->getSubjectLocality()?->toXML($e); + + foreach ($this->getAuthorityBinding() as $ab) { + $ab->toXML($e); + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractAuthorityBindingType.php b/src/SAML11/XML/saml/AbstractAuthorityBindingType.php new file mode 100644 index 0000000..c354434 --- /dev/null +++ b/src/SAML11/XML/saml/AbstractAuthorityBindingType.php @@ -0,0 +1,110 @@ +AuthorityKind; + } + + + /** + * Collect the value of the Location-property + * + * @return string + */ + public function getLocation(): string + { + return $this->Location; + } + + + /** + * Collect the value of the Binding-property + * + * @return string + */ + public function getBinding(): string + { + return $this->Binding; + } + + + /** + * Convert XML into an AuthorityBindingType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $AuthorityKind = self::getAttribute($xml, 'AuthorityKind'); + $Location = self::getAttribute($xml, 'Location'); + $Binding = self::getAttribute($xml, 'Binding'); + + return new static($AuthorityKind, $Location, $Binding); + } + + + /** + * Convert this AuthorityBindingType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this AuthorityBindingType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:samlp', C::NS_PROTOCOL); + + $e->setAttribute('AuthorityKind', $this->getAuthorityKind()); + $e->setAttribute('Location', $this->getLocation()); + $e->setAttribute('Binding', $this->getBinding()); + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractNameIdentifierType.php b/src/SAML11/XML/saml/AbstractNameIdentifierType.php new file mode 100644 index 0000000..ce65607 --- /dev/null +++ b/src/SAML11/XML/saml/AbstractNameIdentifierType.php @@ -0,0 +1,105 @@ +setContent($value); + } + + + /** + * Collect the value of the Format-property + * + * @return string|null + */ + public function getFormat(): ?string + { + return $this->Format; + } + + + /** + * Collect the value of the NameQualifier-property + * + * @return string|null + */ + public function getNameQualifier(): ?string + { + return $this->NameQualifier; + } + + + /** + * Convert XML into an NameIdentifier + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $NameQualifier = self::getOptionalAttribute($xml, 'NameQualifier', null); + $Format = self::getOptionalAttribute($xml, 'Format', null); + + return new static($xml->textContent, $NameQualifier, $Format); + } + + + /** + * Convert this NameIdentifierType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this NameIdentifierType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + $e->textContent = $this->getContent(); + + if ($this->getNameQualifier() !== null) { + $e->setAttribute('NameQualifier', $this->getNameQualifier()); + } + + if ($this->getFormat() !== null) { + $e->setAttribute('Format', $this->getFormat()); + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractSamlElement.php b/src/SAML11/XML/saml/AbstractSamlElement.php new file mode 100644 index 0000000..a80fa24 --- /dev/null +++ b/src/SAML11/XML/saml/AbstractSamlElement.php @@ -0,0 +1,22 @@ + extension point. + * + * @package simplesamlphp/saml11 + */ +abstract class AbstractStatement extends AbstractStatementType implements ExtensionPointInterface +{ + use ExtensionPointTrait; + + /** @var string */ + public const LOCALNAME = 'Statement'; + + + /** + * Initialize a custom saml:Statement element. + * + * @param string $type + */ + protected function __construct( + protected string $type, + ) { + } + + + /** + * @inheritDoc + */ + public function getXsiType(): string + { + return $this->type; + } + + + /** + * Convert an XML element into a Statement. + * + * @param \DOMElement $xml The root XML element + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, 'Statement', InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, C::NS_SAML, InvalidDOMElementException::class); + Assert::true( + $xml->hasAttributeNS(C::NS_XSI, 'type'), + 'Missing required xsi:type in element.', + SchemaViolationException::class, + ); + + $type = $xml->getAttributeNS(C::NS_XSI, 'type'); + Assert::validQName($type, SchemaViolationException::class); + + // first, try to resolve the type to a full namespaced version + $qname = explode(':', $type, 2); + if (count($qname) === 2) { + list($prefix, $element) = $qname; + } else { + $prefix = null; + list($element) = $qname; + } + $ns = $xml->lookupNamespaceUri($prefix); + $type = ($ns === null) ? $element : implode(':', [$ns, $element]); + + // now check if we have a handler registered for it + $handler = Utils::getContainer()->getExtensionHandler($type); + if ($handler === null) { + // we don't have a handler, proceed with unknown statement + return new UnknownStatement(new Chunk($xml), $type); + } + + Assert::subclassOf( + $handler, + AbstractStatement::class, + 'Elements implementing Statement must extend \SimpleSAML\SAML11\XML\saml\AbstractStatement.', + ); + return $handler::fromXML($xml); + } + + + /** + * Convert this Statement to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this Statement. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + $e->setAttributeNS( + 'http://www.w3.org/2000/xmlns/', + 'xmlns:' . static::getXsiTypePrefix(), + static::getXsiTypeNamespaceURI(), + ); + + $type = new XMLAttribute(C::NS_XSI, 'xsi', 'type', $this->getXsiType()); + $type->toXML($e); + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractStatementType.php b/src/SAML11/XML/saml/AbstractStatementType.php new file mode 100644 index 0000000..498699e --- /dev/null +++ b/src/SAML11/XML/saml/AbstractStatementType.php @@ -0,0 +1,14 @@ + $confirmationMethod + * @param \SimpleSAML\SAML11\XML\saml\SubjectConfirmationData|null $subjectConfirmationData + * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo + */ + public function __construct( + protected array $confirmationMethod, + protected ?SubjectConfirmationData $subjectConfirmationData = null, + protected ?KeyInfo $keyInfo = null, + ) { + Assert::minCount($confirmationMethod, 1, MissingElementException::class); + Assert::allIsInstanceOf($confirmationMethod, ConfirmationMethod::class, SchemaViolationException::class); + } + + + /** + * Collect the value of the confirmationMethod-property + * + * @return array<\SimpleSAML\SAML11\XML\saml\ConfirmationMethod> + */ + public function getConfirmationMethod(): array + { + return $this->confirmationMethod; + } + + + /** + * Collect the value of the subjectConfirmationData-property + * + * @return \SimpleSAML\SAML11\XML\saml\SubjectConfirmationData|null + */ + public function getSubjectConfirmationData(): ?SubjectConfirmationData + { + return $this->subjectConfirmationData; + } + + + /** + * Collect the value of the keyInfo-property + * + * @return \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null + */ + public function getKeyInfo(): ?KeyInfo + { + return $this->keyInfo; + } + + + /** + * Convert XML into an SubjectConfirmationType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $subjectConfirmationData = SubjectConfirmationData::getChildrenOfClass($xml); + Assert::maxCount($subjectConfirmationData, 1, TooManyElementsException::class); + + $keyInfo = KeyInfo::getChildrenOfClass($xml); + Assert::maxCount($keyInfo, 1, TooManyElementsException::class); + + return new static( + ConfirmationMethod::getChildrenOfClass($xml), + array_pop($subjectConfirmationData), + array_pop($keyInfo), + ); + } + + + /** + * Convert this SubjectConfirmationType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this SubjectConfirmationType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + + foreach ($this->getConfirmationMethod() as $confirmationMethod) { + $confirmationMethod->toXML($e); + } + + $this->getSubjectConfirmationData()?->toXML($e); + $this->getKeyInfo()?->toXML($e); + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractSubjectLocalityType.php b/src/SAML11/XML/saml/AbstractSubjectLocalityType.php new file mode 100644 index 0000000..f48b7de --- /dev/null +++ b/src/SAML11/XML/saml/AbstractSubjectLocalityType.php @@ -0,0 +1,108 @@ +IPAddress; + } + + + /** + * Collect the value of the DNSAddress-property + * + * @return string|null + */ + public function getDNSAddress(): string|null + { + return $this->DNSAddress; + } + + + /** + * Test if an object, at the state it's in, would produce an empty XML-element + * + * @return bool + */ + public function isEmptyElement(): bool + { + return empty($this->getIPAddress()) + && empty($this->getDNSAddress()); + } + + + /** + * Convert XML into an SubjectLocalityType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $IPAddress = self::getOptionalAttribute($xml, 'IPAddress'); + $DNSAddress = self::getOptionalAttribute($xml, 'DNSAddress'); + + return new static($IPAddress, $DNSAddress); + } + + + /** + * Convert this SubjectLocalityType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this SubjectLocalityType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + + if ($this->getIPAddress() !== null) { + $e->setAttribute('IPAddress', $this->getIPAddress()); + } + + if ($this->getDNSAddress() !== null) { + $e->setAttribute('DNSAddress', $this->getDNSAddress()); + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/AbstractSubjectType.php b/src/SAML11/XML/saml/AbstractSubjectType.php new file mode 100644 index 0000000..0649fdf --- /dev/null +++ b/src/SAML11/XML/saml/AbstractSubjectType.php @@ -0,0 +1,101 @@ +subjectConfirmation; + } + + + /** + * Collect the value of the nameIdentifier-property + * + * @return \SimpleSAML\SAML11\XML\saml\NameIdentifier|null + */ + public function getNameIdentifier(): ?NameIdentifier + { + return $this->nameIdentifier; + } + + + /** + * Convert XML into an SubjectType + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $subjectConfirmation = SubjectConfirmation::getChildrenOfClass($xml); + Assert::maxCount($subjectConfirmation, 1, TooManyElementsException::class); + + $nameIdentifier = NameIdentifier::getChildrenOfClass($xml); + Assert::maxCount($nameIdentifier, 1, TooManyElementsException::class); + + return new static( + array_pop($subjectConfirmation), + array_pop($nameIdentifier), + ); + } + + + /** + * Convert this SubjectType to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this SubjectType. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + + $this->getNameIdentifier()?->toXML($e); + $this->getSubjectConfirmation()?->toXML($e); + + return $e; + } +} diff --git a/src/SAML11/XML/saml/Action.php b/src/SAML11/XML/saml/Action.php new file mode 100644 index 0000000..199efc1 --- /dev/null +++ b/src/SAML11/XML/saml/Action.php @@ -0,0 +1,14 @@ +setContent($content); + } + + + /** + * Validate the content of the element. + * + * @param string $content The value to go in the XML textContent + * @throws \Exception on failure + * @return void + */ + protected function validateContent(string $content): void + { + Assert::validNCName($content, SchemaViolationException::class); // Covers the empty string + } + + + /** + * Convert XML into an AssertionIDReference + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * If the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + return new static($xml->textContent); + } + + + /** + * Convert this AssertionIDReference to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this AssertionIDReference. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $element = $this->instantiateParentElement($parent); + $element->textContent = $this->getContent(); + + return $element; + } +} diff --git a/src/SAML11/XML/saml/Attribute.php b/src/SAML11/XML/saml/Attribute.php new file mode 100644 index 0000000..5f2379c --- /dev/null +++ b/src/SAML11/XML/saml/Attribute.php @@ -0,0 +1,14 @@ +AttributeName; + } + + + /** + * Collect the value of the AttributeNamespace-property + * + * @return string + */ + public function getAttributeNamespace(): string + { + return $this->AttributeNamespace; + } +} diff --git a/src/SAML11/XML/saml/AttributeValue.php b/src/SAML11/XML/saml/AttributeValue.php new file mode 100644 index 0000000..89bcf08 --- /dev/null +++ b/src/SAML11/XML/saml/AttributeValue.php @@ -0,0 +1,168 @@ +value); + + switch ($type) { + case "integer": + return "xs:integer"; + case "NULL": + return "xs:nil"; + case "object": + return sprintf( + '%s:%s', + $this->value::getNamespacePrefix(), + AbstractElement::getClassName(get_class($this->value)), + ); + default: + return "xs:string"; + } + } + + + /** + * Get this attribute value. + * + * @return string|int|\SimpleSAML\XML\AbstractElement[]|null + */ + public function getValue() + { + return $this->value; + } + + + /** + * Convert XML into a AttributeValue + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + if ($xml->childElementCount > 0) { + $node = $xml->firstElementChild; + + if (str_contains($node->tagName, ':')) { + list($prefix, $eltName) = explode(':', $node->tagName); + $className = sprintf('\SimpleSAML\SAML11\XML\%s\%s', $prefix, $eltName); + + if (class_exists($className)) { + $value = $className::fromXML($node); + } else { + $value = Chunk::fromXML($node); + } + } else { + $value = Chunk::fromXML($node); + } + } elseif ( + $xml->hasAttributeNS(C::NS_XSI, "type") && + $xml->getAttributeNS(C::NS_XSI, "type") === "xs:integer" + ) { + // we have an integer as value + $value = intval($xml->textContent); + } elseif ( + // null value + $xml->hasAttributeNS(C::NS_XSI, "nil") && + ($xml->getAttributeNS(C::NS_XSI, "nil") === "1" || + $xml->getAttributeNS(C::NS_XSI, "nil") === "true") + ) { + $value = null; + } else { + $value = $xml->textContent; + } + + return new static($value); + } + + + /** + * Append this attribute value to an element. + * + * @param \DOMElement|null $parent The element we should append this attribute value to. + * + * @return \DOMElement The generated AttributeValue element. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::instantiateParentElement($parent); + + $type = gettype($this->value); + + switch ($type) { + case "integer": + // make sure that the xs namespace is available in the AttributeValue + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI); + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xs', C::NS_XS); + $e->setAttributeNS(C::NS_XSI, 'xsi:type', 'xs:integer'); + $e->textContent = strval($this->getValue()); + break; + case "NULL": + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI); + $e->setAttributeNS(C::NS_XSI, 'xsi:nil', '1'); + $e->textContent = ''; + break; + case "object": + $this->getValue()->toXML($e); + break; + default: // string + $e->textContent = $this->getValue(); + break; + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/Audience.php b/src/SAML11/XML/saml/Audience.php new file mode 100644 index 0000000..e71e8b6 --- /dev/null +++ b/src/SAML11/XML/saml/Audience.php @@ -0,0 +1,30 @@ +setContent($value); + } +} diff --git a/src/SAML11/XML/saml/AuthenticationStatement.php b/src/SAML11/XML/saml/AuthenticationStatement.php new file mode 100644 index 0000000..35a88d6 --- /dev/null +++ b/src/SAML11/XML/saml/AuthenticationStatement.php @@ -0,0 +1,14 @@ +setContent($content); + } + + + /** + * Validate the content of the element. + * + * @param string $content The value to go in the XML textContent + * @throws \Exception on failure + * @return void + */ + protected function validateContent(string $content): void + { + Assert::validNCName($content, SchemaViolationException::class); // Covers the empty string + } + + + /** + * Convert XML into an ConfirmationMethod + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * If the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + return new static($xml->textContent); + } + + + /** + * Convert this ConfirmationMethod to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this ConfirmationMethod. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $element = $this->instantiateParentElement($parent); + $element->textContent = $this->getContent(); + + return $element; + } +} diff --git a/src/SAML11/XML/saml/DecisionTypeEnum.php b/src/SAML11/XML/saml/DecisionTypeEnum.php new file mode 100644 index 0000000..4b92a05 --- /dev/null +++ b/src/SAML11/XML/saml/DecisionTypeEnum.php @@ -0,0 +1,12 @@ +value); + + switch ($type) { + case "integer": + return "xs:integer"; + case "NULL": + return "xs:nil"; + case "object": + return sprintf( + '%s:%s', + $this->value::getNamespacePrefix(), + AbstractElement::getClassName(get_class($this->value)), + ); + default: + return "xs:string"; + } + } + + + /** + * Get this attribute value. + * + * @return string|int|\SimpleSAML\XML\AbstractElement[]|null + */ + public function getValue() + { + return $this->value; + } + + + /** + * Convert XML into a SubjectConfirmationData + * + * @param \DOMElement $xml The XML element we should load + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + if ($xml->childElementCount > 0) { + $node = $xml->firstElementChild; + + if (str_contains($node->tagName, ':')) { + list($prefix, $eltName) = explode(':', $node->tagName); + $className = sprintf('\SimpleSAML\SAML11\XML\%s\%s', $prefix, $eltName); + + if (class_exists($className)) { + $value = $className::fromXML($node); + } else { + $value = Chunk::fromXML($node); + } + } else { + $value = Chunk::fromXML($node); + } + } elseif ( + $xml->hasAttributeNS(C::NS_XSI, "type") && + $xml->getAttributeNS(C::NS_XSI, "type") === "xs:integer" + ) { + // we have an integer as value + $value = intval($xml->textContent); + } elseif ( + // null value + $xml->hasAttributeNS(C::NS_XSI, "nil") && + ($xml->getAttributeNS(C::NS_XSI, "nil") === "1" || + $xml->getAttributeNS(C::NS_XSI, "nil") === "true") + ) { + $value = null; + } else { + $value = $xml->textContent; + } + + return new static($value); + } + + + /** + * Append this attribute value to an element. + * + * @param \DOMElement|null $parent The element we should append this attribute value to. + * + * @return \DOMElement The generated SubjectConfirmationData element. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::instantiateParentElement($parent); + + $type = gettype($this->value); + + switch ($type) { + case "integer": + // make sure that the xs namespace is available in the SubjectConfirmationData + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI); + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xs', C::NS_XS); + $e->setAttributeNS(C::NS_XSI, 'xsi:type', 'xs:integer'); + $e->textContent = strval($this->getValue()); + break; + case "NULL": + $e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI); + $e->setAttributeNS(C::NS_XSI, 'xsi:nil', '1'); + $e->textContent = ''; + break; + case "object": + $this->getValue()->toXML($e); + break; + default: // string + $e->textContent = $this->getValue(); + break; + } + + return $e; + } +} diff --git a/src/SAML11/XML/saml/SubjectLocality.php b/src/SAML11/XML/saml/SubjectLocality.php new file mode 100644 index 0000000..2629972 --- /dev/null +++ b/src/SAML11/XML/saml/SubjectLocality.php @@ -0,0 +1,14 @@ +setClock($systemClock); +ContainerSingleton::setContainer($container); diff --git a/tests/resources/xml/saml_Action.xml b/tests/resources/xml/saml_Action.xml new file mode 100644 index 0000000..15a6214 --- /dev/null +++ b/tests/resources/xml/saml_Action.xml @@ -0,0 +1 @@ +urn:x-simplesamlphp:action diff --git a/tests/resources/xml/saml_AssertionIDReference.xml b/tests/resources/xml/saml_AssertionIDReference.xml new file mode 100644 index 0000000..1395113 --- /dev/null +++ b/tests/resources/xml/saml_AssertionIDReference.xml @@ -0,0 +1 @@ +_Test diff --git a/tests/resources/xml/saml_Attribute.xml b/tests/resources/xml/saml_Attribute.xml new file mode 100644 index 0000000..3947025 --- /dev/null +++ b/tests/resources/xml/saml_Attribute.xml @@ -0,0 +1,4 @@ + + FirstValue + SecondValue + diff --git a/tests/resources/xml/saml_AttributeValue.xml b/tests/resources/xml/saml_AttributeValue.xml new file mode 100644 index 0000000..2743e92 --- /dev/null +++ b/tests/resources/xml/saml_AttributeValue.xml @@ -0,0 +1 @@ +2 diff --git a/tests/resources/xml/saml_Audience.xml b/tests/resources/xml/saml_Audience.xml new file mode 100644 index 0000000..96193b5 --- /dev/null +++ b/tests/resources/xml/saml_Audience.xml @@ -0,0 +1 @@ +urn:x-simplesamlphp:audience diff --git a/tests/resources/xml/saml_AuthenticationStatement.xml b/tests/resources/xml/saml_AuthenticationStatement.xml new file mode 100644 index 0000000..fa5e1e2 --- /dev/null +++ b/tests/resources/xml/saml_AuthenticationStatement.xml @@ -0,0 +1,19 @@ + + + + _Test1 + _Test2 + 2 + + testkey + + MIICxDCCAi2gAwIBAgIUZ9QDx+SBFHednUWDFGm9tyVKrgQwDQYJKoZIhvcNAQELBQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcGA1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNVBAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjIxMjAzMTAzNTQwWhgPMjEyMjExMDkxMDM1NDBaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5vcmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDessdFRVDTMQQW3Na81B1CjJV1tmY3nopoIhZrkbDxLa+pv7jGDRcYreyu1DoQxEs06V2nHLoyOPhqJXSFivqtUwVYhR6NYgbNI6RRSsIJCweH0YOdlHna7gULPcLX0Bfbi4odStaFwG9yzDySwSEPtsKxm5pENPjNVGh+jJ+H/QIDAQABo1MwUTAdBgNVHQ4EFgQUvV75t8EoQo2fVa0E9otdtIGK5X0wHwYDVR0jBBgwFoAUvV75t8EoQo2fVa0E9otdtIGK5X0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQANQUeiwPJXkWMXuaDHToEBKcezYGqGEYnGUi9LMjeb+Kln7X8nn5iknlz4k77rWCbSwLPC/WDr0ySYQA+HagaeUaFpoiYFJKS6uFlK1HYWnM3W4PUiGHg1/xeZlMO44wTwybXVo0y9KMhchfB5XNbDdoJcqWYvi6xtmZZNRbxUyw== + /CN=selfsigned.simplesamlphp.org/O=SimpleSAMLphp HQ/L=Honolulu/ST=Hawaii/C=US + + some + + + + + + diff --git a/tests/resources/xml/saml_AuthorityBinding.xml b/tests/resources/xml/saml_AuthorityBinding.xml new file mode 100644 index 0000000..8d15770 --- /dev/null +++ b/tests/resources/xml/saml_AuthorityBinding.xml @@ -0,0 +1 @@ + diff --git a/tests/resources/xml/saml_ConfirmationMethod.xml b/tests/resources/xml/saml_ConfirmationMethod.xml new file mode 100644 index 0000000..49419cf --- /dev/null +++ b/tests/resources/xml/saml_ConfirmationMethod.xml @@ -0,0 +1 @@ +_Test diff --git a/tests/resources/xml/saml_NameIdentifier.xml b/tests/resources/xml/saml_NameIdentifier.xml new file mode 100644 index 0000000..f69ff50 --- /dev/null +++ b/tests/resources/xml/saml_NameIdentifier.xml @@ -0,0 +1 @@ +TheNameIDValue diff --git a/tests/resources/xml/saml_Subject.xml b/tests/resources/xml/saml_Subject.xml new file mode 100644 index 0000000..ed93a9d --- /dev/null +++ b/tests/resources/xml/saml_Subject.xml @@ -0,0 +1,16 @@ + + TheNameIDValue + + _Test1 + _Test2 + 2 + + testkey + + MIICxDCCAi2gAwIBAgIUZ9QDx+SBFHednUWDFGm9tyVKrgQwDQYJKoZIhvcNAQELBQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcGA1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNVBAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjIxMjAzMTAzNTQwWhgPMjEyMjExMDkxMDM1NDBaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5vcmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDessdFRVDTMQQW3Na81B1CjJV1tmY3nopoIhZrkbDxLa+pv7jGDRcYreyu1DoQxEs06V2nHLoyOPhqJXSFivqtUwVYhR6NYgbNI6RRSsIJCweH0YOdlHna7gULPcLX0Bfbi4odStaFwG9yzDySwSEPtsKxm5pENPjNVGh+jJ+H/QIDAQABo1MwUTAdBgNVHQ4EFgQUvV75t8EoQo2fVa0E9otdtIGK5X0wHwYDVR0jBBgwFoAUvV75t8EoQo2fVa0E9otdtIGK5X0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQANQUeiwPJXkWMXuaDHToEBKcezYGqGEYnGUi9LMjeb+Kln7X8nn5iknlz4k77rWCbSwLPC/WDr0ySYQA+HagaeUaFpoiYFJKS6uFlK1HYWnM3W4PUiGHg1/xeZlMO44wTwybXVo0y9KMhchfB5XNbDdoJcqWYvi6xtmZZNRbxUyw== + /CN=selfsigned.simplesamlphp.org/O=SimpleSAMLphp HQ/L=Honolulu/ST=Hawaii/C=US + + some + + + diff --git a/tests/resources/xml/saml_SubjectConfirmation.xml b/tests/resources/xml/saml_SubjectConfirmation.xml new file mode 100644 index 0000000..8b20dc1 --- /dev/null +++ b/tests/resources/xml/saml_SubjectConfirmation.xml @@ -0,0 +1,13 @@ + + _Test1 + _Test2 + 2 + + testkey + + MIICxDCCAi2gAwIBAgIUZ9QDx+SBFHednUWDFGm9tyVKrgQwDQYJKoZIhvcNAQELBQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcGA1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNVBAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjIxMjAzMTAzNTQwWhgPMjEyMjExMDkxMDM1NDBaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5vcmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDessdFRVDTMQQW3Na81B1CjJV1tmY3nopoIhZrkbDxLa+pv7jGDRcYreyu1DoQxEs06V2nHLoyOPhqJXSFivqtUwVYhR6NYgbNI6RRSsIJCweH0YOdlHna7gULPcLX0Bfbi4odStaFwG9yzDySwSEPtsKxm5pENPjNVGh+jJ+H/QIDAQABo1MwUTAdBgNVHQ4EFgQUvV75t8EoQo2fVa0E9otdtIGK5X0wHwYDVR0jBBgwFoAUvV75t8EoQo2fVa0E9otdtIGK5X0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQANQUeiwPJXkWMXuaDHToEBKcezYGqGEYnGUi9LMjeb+Kln7X8nn5iknlz4k77rWCbSwLPC/WDr0ySYQA+HagaeUaFpoiYFJKS6uFlK1HYWnM3W4PUiGHg1/xeZlMO44wTwybXVo0y9KMhchfB5XNbDdoJcqWYvi6xtmZZNRbxUyw== + /CN=selfsigned.simplesamlphp.org/O=SimpleSAMLphp HQ/L=Honolulu/ST=Hawaii/C=US + + some + + diff --git a/tests/resources/xml/saml_SubjectConfirmationData.xml b/tests/resources/xml/saml_SubjectConfirmationData.xml new file mode 100644 index 0000000..98262f0 --- /dev/null +++ b/tests/resources/xml/saml_SubjectConfirmationData.xml @@ -0,0 +1 @@ +2 diff --git a/tests/resources/xml/saml_SubjectLocality.xml b/tests/resources/xml/saml_SubjectLocality.xml new file mode 100644 index 0000000..bebdf9d --- /dev/null +++ b/tests/resources/xml/saml_SubjectLocality.xml @@ -0,0 +1 @@ + diff --git a/tests/src/SAML11/XML/saml/ActionTest.php b/tests/src/SAML11/XML/saml/ActionTest.php new file mode 100644 index 0000000..97ea957 --- /dev/null +++ b/tests/src/SAML11/XML/saml/ActionTest.php @@ -0,0 +1,61 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($action), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/AssertionIDReferenceTest.php b/tests/src/SAML11/XML/saml/AssertionIDReferenceTest.php new file mode 100644 index 0000000..37205b7 --- /dev/null +++ b/tests/src/SAML11/XML/saml/AssertionIDReferenceTest.php @@ -0,0 +1,54 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($assertionIDReference), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/AttributeTest.php b/tests/src/SAML11/XML/saml/AttributeTest.php new file mode 100644 index 0000000..7e4047c --- /dev/null +++ b/tests/src/SAML11/XML/saml/AttributeTest.php @@ -0,0 +1,64 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($attribute), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/AttributeValueTest.php b/tests/src/SAML11/XML/saml/AttributeValueTest.php new file mode 100644 index 0000000..86c1cf7 --- /dev/null +++ b/tests/src/SAML11/XML/saml/AttributeValueTest.php @@ -0,0 +1,141 @@ +assertIsInt($av->getValue()); + $this->assertEquals(2, $av->getValue()); + $this->assertEquals('xs:integer', $av->getXsiType()); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($av), + ); + } + + + /** + * Test creating an AttributeValue from scratch using a string. + */ + public function testMarshallingString(): void + { + $av = new AttributeValue('value'); + + $this->assertEquals('value', $av->getValue()); + $this->assertEquals('xs:string', $av->getXsiType()); + } + + + /** + */ + public function testMarshallingNull(): void + { + $av = new AttributeValue(null); + $this->assertNull($av->getValue()); + $this->assertEquals('xs:nil', $av->getXsiType()); + $nssaml = C::NS_SAML; + $nsxsi = C::NS_XSI; + $xml = << +XML; + $this->assertEquals( + $xml, + strval($av), + ); + } + + + /** + * Verifies that supplying an empty string as attribute value will + * generate a tag with no content (instead of e.g. an empty tag). + * + */ + public function testEmptyStringAttribute(): void + { + $av = new AttributeValue(''); + $xmlRepresentation = clone self::$xmlRepresentation; + $xmlRepresentation->documentElement->textContent = ''; +// $this->assertEqualXMLStructure( +// $this->xmlRepresentation->documentElement, +// $av->toXML(), +// ); + $this->assertEquals('', $av->getValue()); + $this->assertEquals('xs:string', $av->getXsiType()); + } + + + // unmarshalling + + + /** + * Verifies that we can create an AttributeValue containing a NameID from a DOMElement. + * + * @return void + */ + public function testUnmarshallingNameID(): void + { + $document = DOMDocumentFactory::fromString(<< + abcd-some-value-xyz + +XML + ); + + $av = AttributeValue::fromXML($document->documentElement); + $value = $av->getValue(); + + $this->assertInstanceOf(NameIdentifier::class, $value); + + $this->assertEquals('abcd-some-value-xyz', $value->getContent()); + $this->assertEquals('urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', $value->getFormat()); + $this->assertXmlStringEqualsXmlString($document->saveXML(), $av->toXML()->ownerDocument?->saveXML()); + } +} diff --git a/tests/src/SAML11/XML/saml/AudienceTest.php b/tests/src/SAML11/XML/saml/AudienceTest.php new file mode 100644 index 0000000..8c18f1d --- /dev/null +++ b/tests/src/SAML11/XML/saml/AudienceTest.php @@ -0,0 +1,60 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($audience), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/AuthenticationStatementTest.php b/tests/src/SAML11/XML/saml/AuthenticationStatementTest.php new file mode 100644 index 0000000..9093c49 --- /dev/null +++ b/tests/src/SAML11/XML/saml/AuthenticationStatementTest.php @@ -0,0 +1,177 @@ +some' + )->documentElement), + ], + 'fed654', + ); + + $sc = new SubjectConfirmation( + [new ConfirmationMethod('_Test1'), new ConfirmationMethod('_Test2')], + $scd, + $keyInfo, + ); + + $nameIdentifier = new NameIdentifier( + 'TheNameIDValue', + 'TheNameQualifier', + 'urn:the:format', + ); + + $subject = new Subject($sc, $nameIdentifier); + + $subjectLocality = new SubjectLocality('127.0.0.1', 'simplesamlphp.org'); + $authorityBinding = new AuthorityBinding( + 'samlp:AttributeQuery', + 'urn:x-simplesamlphp:location', + 'urn:x-simplesamlphp:binding', + ); + + $authenticationStatement = new AuthenticationStatement( + C::AC_PASSWORD, + new DateTimeImmutable('2023-01-24T09:42:26Z'), + $subjectLocality, + [$authorityBinding], + ); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($authenticationStatement), + ); + } + + + public function testMarshallingElementOrdering(): void + { + $authenticationStatement = AuthenticationStatement::fromXML(self::$xmlRepresentation->documentElement); + $authenticationStatementElement = $authenticationStatement->toXML(); + + // Test for a SubjectLocality + $xpCache = XPath::getXPath($authenticationStatementElement); + $authenticationStatementElements = XPath::xpQuery( + $authenticationStatementElement, + './saml_assertion:SubjectLocality', + $xpCache, + ); + $this->assertCount(1, $authenticationStatementElements); + + // Test ordering of AuthenticationStatement contents + /** @psalm-var \DOMElement[] $authnStatementElements */ + $authenticationStatementElements = XPath::xpQuery( + $authenticationStatementElement, + './saml_assertion:SubjectLocality/following-sibling::*', + $xpCache, + ); + $this->assertCount(1, $authenticationStatementElements); + $this->assertEquals('saml:AuthorityBinding', $authenticationStatementElements[0]->tagName); + } +} diff --git a/tests/src/SAML11/XML/saml/AuthorityBindingTest.php b/tests/src/SAML11/XML/saml/AuthorityBindingTest.php new file mode 100644 index 0000000..94ae1d2 --- /dev/null +++ b/tests/src/SAML11/XML/saml/AuthorityBindingTest.php @@ -0,0 +1,61 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($ab), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/ConfirmationMethodTest.php b/tests/src/SAML11/XML/saml/ConfirmationMethodTest.php new file mode 100644 index 0000000..26f3102 --- /dev/null +++ b/tests/src/SAML11/XML/saml/ConfirmationMethodTest.php @@ -0,0 +1,54 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($confirmationMethod), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/NameIdentifierTest.php b/tests/src/SAML11/XML/saml/NameIdentifierTest.php new file mode 100644 index 0000000..2af30a8 --- /dev/null +++ b/tests/src/SAML11/XML/saml/NameIdentifierTest.php @@ -0,0 +1,63 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($nameIdentifier), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/SubjectConfirmationDataTest.php b/tests/src/SAML11/XML/saml/SubjectConfirmationDataTest.php new file mode 100644 index 0000000..fc8b00d --- /dev/null +++ b/tests/src/SAML11/XML/saml/SubjectConfirmationDataTest.php @@ -0,0 +1,142 @@ +assertIsInt($scd->getValue()); + $this->assertEquals(2, $scd->getValue()); + $this->assertEquals('xs:integer', $scd->getXsiType()); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($scd), + ); + } + + + /** + * Test creating an SubjectConfirmationData from scratch using a string. + */ + public function testMarshallingString(): void + { + $scd = new SubjectConfirmationData('value'); + + $this->assertEquals('value', $scd->getValue()); + $this->assertEquals('xs:string', $scd->getXsiType()); + } + + + /** + */ + public function testMarshallingNull(): void + { + $scd = new SubjectConfirmationData(null); + $this->assertNull($scd->getValue()); + $this->assertEquals('xs:nil', $scd->getXsiType()); + $nssaml = C::NS_SAML; + $nsxsi = C::NS_XSI; + $xml = << +XML; + $this->assertEquals( + $xml, + strval($scd), + ); + } + + + /** + * Verifies that supplying an empty string as subject confirmation data will + * generate a tag with no content (instead of e.g. an empty tag). + * + */ + public function testEmptyStringAttribute(): void + { + $scd = new SubjectConfirmationData(''); + $xmlRepresentation = clone self::$xmlRepresentation; + $xmlRepresentation->documentElement->textContent = ''; +// $this->assertEqualXMLStructure( +// $this->xmlRepresentation->documentElement, +// $scd->toXML(), +// ); + $this->assertEquals('', $scd->getValue()); + $this->assertEquals('xs:string', $scd->getXsiType()); + } + + + // unmarshalling + + + /** + * Verifies that we can create an SubjectConfirmationData containing a NameID from a DOMElement. + * + * @return void + */ + public function testUnmarshallingNameID(): void + { + $document = DOMDocumentFactory::fromString(<< + abcd-some-value-xyz + +XML + ); + + $scd = SubjectConfirmationData::fromXML($document->documentElement); + $value = $scd->getValue(); + + $this->assertInstanceOf(NameIdentifier::class, $value); + + $this->assertEquals('abcd-some-value-xyz', $value->getContent()); + $this->assertEquals('urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', $value->getFormat()); + $this->assertXmlStringEqualsXmlString($document->saveXML(), $scd->toXML()->ownerDocument?->saveXML()); + } +} diff --git a/tests/src/SAML11/XML/saml/SubjectConfirmationTest.php b/tests/src/SAML11/XML/saml/SubjectConfirmationTest.php new file mode 100644 index 0000000..76bff15 --- /dev/null +++ b/tests/src/SAML11/XML/saml/SubjectConfirmationTest.php @@ -0,0 +1,124 @@ +some' + )->documentElement), + ], + 'fed654', + ); + + $sc = new SubjectConfirmation( + [new ConfirmationMethod('_Test1'), new ConfirmationMethod('_Test2')], + $scd, + $keyInfo, + ); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($sc), + ); + } +} diff --git a/tests/src/SAML11/XML/saml/SubjectLocalityTest.php b/tests/src/SAML11/XML/saml/SubjectLocalityTest.php new file mode 100644 index 0000000..3343e0b --- /dev/null +++ b/tests/src/SAML11/XML/saml/SubjectLocalityTest.php @@ -0,0 +1,71 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($sl), + ); + } + + + /** + * Test creating an empty SubjectLocality from scratch + */ + public function testMarshallingEmpty(): void + { + $sl = new SubjectLocality(); + $this->assertTrue($sl->isEmptyElement()); + } +} diff --git a/tests/src/SAML11/XML/saml/SubjectTest.php b/tests/src/SAML11/XML/saml/SubjectTest.php new file mode 100644 index 0000000..bbea062 --- /dev/null +++ b/tests/src/SAML11/XML/saml/SubjectTest.php @@ -0,0 +1,132 @@ +some' + )->documentElement), + ], + 'fed654', + ); + + $sc = new SubjectConfirmation( + [new ConfirmationMethod('_Test1'), new ConfirmationMethod('_Test2')], + $scd, + $keyInfo, + ); + + $nameIdentifier = new NameIdentifier( + 'TheNameIDValue', + 'TheNameQualifier', + 'urn:the:format', + ); + + $subject = new Subject($sc, $nameIdentifier); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($subject), + ); + } +} diff --git a/tools/composer-require-checker.json b/tools/composer-require-checker.json new file mode 100644 index 0000000..eed71aa --- /dev/null +++ b/tools/composer-require-checker.json @@ -0,0 +1,4 @@ +{ + "symbol-whitelist": [ + ] +} diff --git a/tools/linters/.markdown-lint.yml b/tools/linters/.markdown-lint.yml new file mode 100644 index 0000000..a076d91 --- /dev/null +++ b/tools/linters/.markdown-lint.yml @@ -0,0 +1,4 @@ +--- +default: true + +MD013: false diff --git a/tools/linters/.yaml-lint.yml b/tools/linters/.yaml-lint.yml new file mode 100644 index 0000000..630095a --- /dev/null +++ b/tools/linters/.yaml-lint.yml @@ -0,0 +1,7 @@ +--- + +extends: default + +rules: + line-length: + max: 120