diff --git a/docs/simplesamlphp-changelog.md b/docs/simplesamlphp-changelog.md index 1e61d5b537a394d890b89bc004e5b163f6b1b958..3e1f8ec3a8debf367b5e22bca99f60c8f3763d87 100644 --- a/docs/simplesamlphp-changelog.md +++ b/docs/simplesamlphp-changelog.md @@ -12,8 +12,6 @@ See the upgrade notes for specific information about upgrading. * Support for SAML 1.1 was removed * Old-style PHP templates were removed * Old-style dictionaries were removed - * core:PairwiseID and core:SubjectID authprocs no longer support the 'scope' config-setting. - Use 'scopeAttribute' instead to identify the attribute holding the scope. ## Version 1.19.1 diff --git a/modules/core/docs/authproc_pairwiseid.md b/modules/core/docs/authproc_pairwiseid.md deleted file mode 100644 index 75b20209ff3a32a9e8c32adb63084b8122aeee0c..0000000000000000000000000000000000000000 --- a/modules/core/docs/authproc_pairwiseid.md +++ /dev/null @@ -1,23 +0,0 @@ -`core:PairwiseID` -=================== - -Filter to insert a pairwise-id that complies with the following specification; -http://docs.oasis-open.org/security/saml-subject-id-attr/v1.0/saml-subject-id-attr-v1.0.pdf - -This filter will take an attribute and a scope as input and transforms this into a anonymized and scoped -identifier that is globally unique for a given user & service provider combination. - -Note: -Since the subject-id is specified as single-value attribute, only the first value of `identifyingAttribute` - and `scopeAttribute` are considered. - -Examples --------- - - 'authproc' => [ - 50 => [ - 'class' => 'core:PairwiseID', - 'identifyingAttribute' => 'uid', - 'scopeAttribute' => 'scope', - ], - ], diff --git a/modules/core/docs/authproc_subjectid.md b/modules/core/docs/authproc_subjectid.md deleted file mode 100644 index 85347c2ca622be00251cb7088ef05b88db9e3b4e..0000000000000000000000000000000000000000 --- a/modules/core/docs/authproc_subjectid.md +++ /dev/null @@ -1,26 +0,0 @@ -`core:SubjectID` -=================== - -Filter to insert a subject-id that complies with the following specification; -http://docs.oasis-open.org/security/saml-subject-id-attr/v1.0/saml-subject-id-attr-v1.0.pdf - -This filter will take an attribute and a scope as input and transforms this into a scoped identifier that is globally unique for a given user. - -Note: ------ -If privacy is of your concern, you may want to use the PairwiseID-filter instead. - -Note: -Since the subject-id is specified as single-value attribute, only the first value of `identifyingAttribute` - and `scopeAttribute` are considered. - -Examples --------- - - 'authproc' => [ - 50 => [ - 'class' => 'core:SubjectID', - 'identifyingAttribute' => 'uid', - 'scopeAttribute' => 'scope', - ], - ], diff --git a/modules/core/lib/Auth/Process/PairwiseID.php b/modules/core/lib/Auth/Process/PairwiseID.php deleted file mode 100644 index edfe0deb0e026b6f108cd8bf070ea8cfd02dfb6b..0000000000000000000000000000000000000000 --- a/modules/core/lib/Auth/Process/PairwiseID.php +++ /dev/null @@ -1,108 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Module\core\Auth\Process; - -use Exception; -use SAML2\Constants; -use SAML2\XML\saml\NameID; -use SimpleSAML\Assert\Assert; -use SimpleSAML\Auth; -use SimpleSAML\Utils; - -/** - * Filter to generate the Pairwise ID attribute. - * - * See: http://docs.oasis-open.org/security/saml-subject-id-attr/v1.0/csprd01/saml-subject-id-attr-v1.0-csprd01.html - * - * By default, this filter will generate the ID based on the UserID of the current user. - * This is generated from the attribute configured in 'identifyingAttribute' in the - * authproc-configuration. - * - * NOTE: since the subject-id is specified as single-value attribute, only the first value of `identifyingAttribute` - * and `scopeAttribute` are considered. - * - * Example - generate from attribute: - * <code> - * 'authproc' => [ - * 50 => [ - * 'core:PairwiseID', - * 'identifyingAttribute' => 'uid', - * 'scopeAttribute' => 'example.org', - * ] - * ] - * </code> - * - * @package SimpleSAMLphp - */ -class PairwiseID extends SubjectID -{ - /** - * The name for this class - */ - public const NAME = 'PairwiseID'; - - /** - * @var \SimpleSAML\Utils\Config - */ - protected $configUtils; - - - /** - * Initialize this filter. - * - * @param array &$config Configuration information about this filter. - * @param mixed $reserved For future use. - */ - public function __construct(array &$config, $reserved) - { - parent::__construct($config, $reserved); - - $this->configUtils = new Utils\Config(); - } - - - /** - * Apply filter to add the Pairwise ID. - * - * @param array &$state The current state. - */ - public function process(array &$state): void - { - $userID = $this->getIdentifyingAttribute($state); - $scope = $this->getScopeAttribute($state); - - if ($scope === null || $userID === null) { - // Attributes missing, precondition not met - return; - } - - if (!empty($state['saml:RequesterID'])) { - // Proxied request - use actual SP entity ID - $sp_entityid = $state['saml:RequesterID'][0]; - } else { - $sp_entityid = $state['core:SP']; - } - - // Calculate hash - $salt = $this->configUtils->getSecretSalt(); - $hash = hash('sha256', $salt . '|' . $userID . '|' . $sp_entityid, false); - - $value = strtolower($hash . '@' . $scope); - $this->validateGeneratedIdentifier($value); - - $state['Attributes'][Constants::ATTR_PAIRWISE_ID] = [$value]; - } - - - /** - * Inject the \SimpleSAML\Utils\Config dependency. - * - * @param \SimpleSAML\Utils\Config $configUtils - */ - public function setConfigUtils(Utils\Config $configUtils): void - { - $this->configUtils = $configUtils; - } -} diff --git a/modules/core/lib/Auth/Process/SubjectID.php b/modules/core/lib/Auth/Process/SubjectID.php deleted file mode 100644 index 25e3039942f3d135d5d762ac0fef6656b54f94ee..0000000000000000000000000000000000000000 --- a/modules/core/lib/Auth/Process/SubjectID.php +++ /dev/null @@ -1,229 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Module\core\Auth\Process; - -use Exception; -use SAML2\Constants; -//use SAML2\Exception\ProtocolViolationException -use SAML2\XML\saml\NameID; -use SimpleSAML\Assert\Assert; -use SimpleSAML\Auth; -use SimpleSAML\Logger; - -/** - * Filter to generate the subject ID attribute. - * - * See: http://docs.oasis-open.org/security/saml-subject-id-attr/v1.0/csprd01/saml-subject-id-attr-v1.0-csprd01.html - * - * By default, this filter will generate the ID based on the UserID of the current user. - * This is generated from the attribute configured in 'identifyingAttribute' in the - * authproc-configuration. - * - * NOTE: since the subject-id is specified as single-value attribute, only the first value of `identifyingAttribute` - * and `scopeAttribute` are considered. - * - * Example - generate from attribute: - * <code> - * 'authproc' => [ - * 50 => [ - * 'core:SubjectID', - * 'identifyingAttribute' => 'uid', - * 'scopeAttribute' => 'scope', - * ] - * ] - * </code> - * - * @package SimpleSAMLphp - */ -class SubjectID extends Auth\ProcessingFilter -{ - /** - * The name for this class - */ - public const NAME = 'SubjectID'; - - /** - * The regular expression to match the scope - * - * @var string - */ - public const SCOPE_PATTERN = '/^[a-z0-9][a-z0-9.-]{0,126}$/i'; - - /** - * The regular expression to match the specifications - * - * @var string - */ - public const SPEC_PATTERN = '/^[a-z0-9][a-z0-9=-]{0,126}@[a-z0-9][a-z0-9.-]{0,126}$/i'; - - /** - * The regular expression to match worrisome identifiers that need to raise a warning - * - * @var string - */ - public const WARN_PATTERN = '/^[a-z0-9][a-z0-9=-]{3,126}@[a-z0-9][a-z0-9.-]{3,126}$/i'; - - /** - * The attribute we should generate the subject id from. - * - * @var string - */ - protected string $identifyingAttribute; - - /** - * The attribute we should use for the scope of the subject id. - * - * @var string - */ - protected string $scopeAttribute; - - /** - * @var \SimpleSAML\Logger|string - * @psalm-var \SimpleSAML\Logger|class-string - */ - protected $logger = Logger::class; - - - /** - * Initialize this filter. - * - * @param array &$config Configuration information about this filter. - * @param mixed $reserved For future use. - */ - public function __construct(array &$config, $reserved) - { - parent::__construct($config, $reserved); - - Assert::keyExists($config, 'identifyingAttribute', "Missing mandatory 'identifyingAttribute' config setting."); - Assert::keyExists($config, 'scopeAttribute', "Missing mandatory 'scope' config setting."); - Assert::stringNotEmpty($config['identifyingAttribute']); - Assert::stringNotEmpty($config['scopeAttribute']); - - $this->identifyingAttribute = $config['identifyingAttribute']; - $this->scopeAttribute = $config['scopeAttribute']; - } - - - /** - * Apply filter to add the subject ID. - * - * @param array &$state The current state. - */ - public function process(array &$state): void - { - $userID = $this->getIdentifyingAttribute($state); - $scope = $this->getScopeAttribute($state); - - if ($scope === null || $userID === null) { - // Attributes missing, precondition not met - return; - } - - $value = strtolower($userID . '@' . $scope); - $this->validateGeneratedIdentifier($value); - - $state['Attributes'][Constants::ATTR_SUBJECT_ID] = [$value]; - } - - - /** - * Retrieve the identifying attribute from the state and test it for erroneous conditions - * - * @param array $state - * @return string|null - * @throws \SimpleSAML\Assert\AssertionFailedException if the pre-conditions are not met - */ - protected function getIdentifyingAttribute(array $state): ?string - { - if (!array_key_exists('Attributes', $state) || !array_key_exists($this->identifyingAttribute, $state['Attributes'])) { - $this->logger::warning( - sprintf( - "core:" . static::NAME . ": Missing attribute '%s', which is needed to generate the ID.", - $this->identifyingAttribute - ) - ); - - return null; - } - - $userID = $state['Attributes'][$this->identifyingAttribute][0]; - Assert::stringNotEmpty($userID, 'core' . static::NAME . ': \'identifyingAttribute\' cannot be an empty string.'); - - return $userID; - } - - - /** - * Retrieve the scope attribute from the state and test it for erroneous conditions - * - * @param array $state - * @return string|null - * @throws \SimpleSAML\Assert\AssertionFailedException if the pre-conditions are not met - */ - protected function getScopeAttribute(array $state): ?string - { - if (!array_key_exists('Attributes', $state) || !array_key_exists($this->scopeAttribute, $state['Attributes'])) { - $this->logger::warning( - sprintf( - "core:" . static::NAME . ": Missing attribute '%s', which is needed to generate the ID.", - $this->scopeAttribute - ) - ); - - return null; - } - - $scope = $state['Attributes'][$this->scopeAttribute][0]; - Assert::stringNotEmpty($scope, 'core' . static::NAME . ': \'scopeAttribute\' cannot be an empty string.'); - - // If the value is scoped, extract the scope from it - if (strpos($scope, '@') !== false) { - $scope = explode('@', $scope, 2); - $scope = $scope[1]; - } - - Assert::regex( - $scope, - self::SCOPE_PATTERN, - 'core:' . static::NAME . ': \'scopeAttribute\' contains illegal characters.' -// ProtocolViolationException::class - ); - return $scope; - } - - - /** - * Test the generated identifier to ensure compliancy with the specifications. - * Log a warning when the generated value is considered to be weak - * - * @param string $value - * @return void - * @throws \SimpleSAML\Assert\AssertionFailedException if the post-conditions are not met - */ - protected function validateGeneratedIdentifier(string $value): void - { - Assert::regex( - $value, - self::SPEC_PATTERN, - 'core:' . static::NAME . ': Generated ID \'' . $value . '\' contains illegal characters.' -// ProtocolViolationException::class - ); - - if (preg_match(self::WARN_PATTERN, $value) === 0) { - $this->logger::warning('core:' . static::NAME . ': Generated ID \'' . $value . '\' can hardly be considered globally unique.'); - } - } - - - /** - * Inject the \SimpleSAML\Logger dependency. - * - * @param \SimpleSAML\Logger $logger - */ - public function setLogger(Logger $logger): void - { - $this->logger = $logger; - } -} diff --git a/tests/modules/core/lib/Auth/Process/PairwiseIDTest.php b/tests/modules/core/lib/Auth/Process/PairwiseIDTest.php deleted file mode 100644 index daa32fc4b3fcbdaabfe509f0bec4abf0f40a6f0e..0000000000000000000000000000000000000000 --- a/tests/modules/core/lib/Auth/Process/PairwiseIDTest.php +++ /dev/null @@ -1,341 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Test\Module\core\Auth\Process; - -use PHPUnit\Framework\TestCase; -use RuntimeException; -use SAML2\Constants; -use SAML2\XML\saml\NameID; -use SimpleSAML\Assert\AssertionFailedException; -use SimpleSAML\Configuration; -use SimpleSAML\Logger; -use SimpleSAML\Module\core\Auth\Process\PairwiseID; -use SimpleSAML\Utils; - -/** - * Test for the core:PairwiseID filter. - * - * @covers \SimpleSAML\Module\core\Auth\Process\PairwiseID - */ -class PairwiseIDTest extends TestCase -{ - /** @var \SimpleSAML\Configuration */ - protected Configuration $config; - - /** @var \SimpleSAML\Utils\Config */ - protected static Utils\Config $configUtils; - - /** @var \SimpleSAML\Logger */ - protected static Logger $logger; - - - /** - * Set up for each test. - */ - protected function setUp(): void - { - parent::setUp(); - - self::$configUtils = new class () extends Utils\Config { - public function getSecretSalt(): string - { - // stub - return 'secretsalt'; - } - }; - - self::$logger = new class () extends Logger { - public static function warning(string $string): void - { - // stub - throw new RuntimeException($string); - } - }; - } - - - /** - * Helper function to run the filter with a given configuration. - * - * @param array $config The filter configuration. - * @param array $request The request state. - * @return array The state array after processing. - */ - private static function processFilter(array $config, array $request): array - { - $filter = new PairwiseID($config, null); - $filter->setConfigUtils(self::$configUtils); - $filter->setLogger(self::$logger); - $filter->process($request); - return $request; - } - - - /** - * Test the most basic functionality - */ - public function testBasic(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['ex-ample.org']], - 'core:SP' => 'urn:sp', - ]; - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $this->assertMatchesRegularExpression( - PairwiseID::SPEC_PATTERN, - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - $this->assertEquals( - '53d4f7fe57fb597ada481e81e0f15048bc610774cbb5614ea38f08ea918ba199@ex-ample.org', - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - } - - - /** - * Test the most basic functionality, but with a scoped scope-attribute - */ - public function testBasicScopedScope(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['u=se-r2@ex-ample.org']], - 'core:SP' => 'urn:sp', - ]; - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $this->assertMatchesRegularExpression( - PairwiseID::SPEC_PATTERN, - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - $this->assertEquals( - '53d4f7fe57fb597ada481e81e0f15048bc610774cbb5614ea38f08ea918ba199@ex-ample.org', - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - } - - - /** - * Test the most basic functionality on proxied request - */ - public function testBasicProxiedRequest(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['ex-ample.org']], - 'saml:RequesterID' => [0 => 'urn:sp'], - ]; - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $this->assertMatchesRegularExpression( - PairwiseID::SPEC_PATTERN, - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - $this->assertEquals( - '53d4f7fe57fb597ada481e81e0f15048bc610774cbb5614ea38f08ea918ba199@ex-ample.org', - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - } - - - /** - * Test the proxied request with multiple hops - */ - public function testProxiedRequestMultipleHops(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['ex-ample.org']], - 'saml:RequesterID' => [0 => 'urn:sp', 1 => 'urn:some:sp', 2 => 'urn:some:other:sp'], - ]; - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $this->assertMatchesRegularExpression( - PairwiseID::SPEC_PATTERN, - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - $this->assertEquals( - '53d4f7fe57fb597ada481e81e0f15048bc610774cbb5614ea38f08ea918ba199@ex-ample.org', - $attributes[Constants::ATTR_PAIRWISE_ID][0] - ); - } - - - /** - * Test that illegal characters in scope throws an exception. - */ - public function testScopeIllegalCharacterThrowsException(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user2'], 'scope' => ['ex%ample.org']], - 'core:SP' => 'urn:sp', - ]; - - $this->expectException(AssertionFailedException::class); - self::processFilter($config, $request); - } - - - /** - * Test that generated ID's for the same user, but different SP's are NOT equal - */ - public function testUniqueIdentifierPerSPSameUser(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user1'], 'scope' => ['example.org']], - 'core:SP' => 'urn:sp', - ]; - - // Generate first ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value1 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - // Switch SP - $request['core:SP'] = 'urn:some:other:sp'; - - // Generate second ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value2 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - $this->assertNotSame($value1, $value2); - } - - - /** - * Test that generated ID's for different users, but the same SP's are NOT equal - */ - public function testUniqueIdentifierPerUserSameSP(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user1'], 'scope' => ['example.org']], - 'core:SP' => 'urn:sp', - ]; - - // Generate first ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value1 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - // Switch user - $request['Attributes']['uid'] = ['user2']; - - // Generate second ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value2 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - $this->assertNotSame($value1, $value2); - } - - - /** - * Test that generated ID's for the same user and same SP, but with a different salt are NOT equal - */ - public function testUniqueIdentifierDifferentSalts(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user1'], 'scope' => ['example.org']], - 'core:SP' => 'urn:sp', - ]; - - // Generate first ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value1 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - // Change the salt - self::$configUtils = new class () extends Utils\Config { - public function getSecretSalt(): string - { - // stub - return 'pepper'; - } - }; - - // Generate second ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value2 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - $this->assertNotSame($value1, $value2); - } - - - /** - * Test that generated ID's for the same user and same SP, but with a different scope are NOT equal - */ - public function testUniqueIdentifierDifferentScopes(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user1'], 'scope' => ['example.org']], - 'core:SP' => 'urn:sp', - ]; - - // Generate first ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value1 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - // Change the scope - $request['Attributes']['scope'] = ['example.edu']; - - // Generate second ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_PAIRWISE_ID, $attributes); - $value2 = $attributes[Constants::ATTR_PAIRWISE_ID][0]; - - $this->assertNotSame($value1, $value2); - - $this->assertMatchesRegularExpression( - '/@example.org$/i', - $value1 - ); - $this->assertMatchesRegularExpression( - '/@example.edu$/i', - $value2 - ); - } - - - /** - * Test that weak identifiers log a warning - */ - public function testWeakIdentifierLogsWarning(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['a'], 'scope' => ['b']], - 'core:SP' => 'urn:sp', - ]; - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'core:PairwiseID: Generated ID \'c5b54935db5e291a6b94688921fa77ced8ce425ce8c61a448bd4997f494dbebe@b\' can hardly be considered globally unique.' - ); - - self::processFilter($config, $request); - } -} diff --git a/tests/modules/core/lib/Auth/Process/SubjectIDTest.php b/tests/modules/core/lib/Auth/Process/SubjectIDTest.php deleted file mode 100644 index f090ffcc2f31578411c9614df17b206e627cdc7c..0000000000000000000000000000000000000000 --- a/tests/modules/core/lib/Auth/Process/SubjectIDTest.php +++ /dev/null @@ -1,216 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Test\Module\core\Auth\Process; - -use PHPUnit\Framework\TestCase; -use RuntimeException; -use SAML2\Constants; -use SAML2\XML\saml\NameID; -use SimpleSAML\Assert\AssertionFailedException; -use SimpleSAML\Configuration; -use SimpleSAML\Logger; -use SimpleSAML\Module\core\Auth\Process\SubjectID; -use SimpleSAML\Utils; - -/** - * Test for the core:SubjectID filter. - * - * @covers \SimpleSAML\Module\core\Auth\Process\SubjectID - */ -class SubjectIDTest extends TestCase -{ - /** @var \SimpleSAML\Configuration */ - protected Configuration $config; - - /** @var \SimpleSAML\Logger */ - protected static Logger $logger; - - - /** - * Set up for each test. - */ - protected function setUp(): void - { - parent::setUp(); - - self::$logger = new class () extends Logger { - public static function warning(string $string): void - { - // stub - throw new RuntimeException($string); - } - }; - } - - - /** - * Helper function to run the filter with a given configuration. - * - * @param array $config The filter configuration. - * @param array $request The request state. - * @return array The state array after processing. - */ - private static function processFilter(array $config, array $request): array - { - $filter = new SubjectID($config, null); - $filter->setLogger(self::$logger); - $filter->process($request); - return $request; - } - - - /** - * Test the most basic functionality - */ - public function testBasic(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['ex-ample.org']], - ]; - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_SUBJECT_ID, $attributes); - $this->assertMatchesRegularExpression( - SubjectID::SPEC_PATTERN, - $attributes[Constants::ATTR_SUBJECT_ID][0] - ); - $this->assertEquals('u=se-r2@ex-ample.org', $attributes[Constants::ATTR_SUBJECT_ID][0]); - } - - - /** - * Test the most basic functionality, but with a scoped scope-attribute - */ - public function testScopedScope(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['u=se-r2@ex-ample.org']], - ]; - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_SUBJECT_ID, $attributes); - $this->assertMatchesRegularExpression( - SubjectID::SPEC_PATTERN, - $attributes[Constants::ATTR_SUBJECT_ID][0] - ); - $this->assertEquals('u=se-r2@ex-ample.org', $attributes[Constants::ATTR_SUBJECT_ID][0]); - } - - - /** - * Test that illegal characters in userID throws an exception. - */ - public function testUserIDIllegalCharacterThrowsException(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['u=se+r2'], 'scope' => ['example.org']], - ]; - - $this->expectException(AssertionFailedException::class); - self::processFilter($config, $request); - } - - - /** - * Test that illegal characters in scope throws an exception. - */ - public function testScopeIllegalCharacterThrowsException(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user2'], 'scope' => ['ex%ample.org']], - ]; - - $this->expectException(AssertionFailedException::class); - self::processFilter($config, $request); - } - - - /** - * Test that generated ID's for different users, but the same SP's are NOT equal - */ - public function testUniqueIdentifierPerUserSameSP(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user1'], 'scope' => ['example.org']], - ]; - - // Generate first ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_SUBJECT_ID, $attributes); - $value1 = $attributes[Constants::ATTR_SUBJECT_ID][0]; - - // Switch user - $request['Attributes']['uid'] = ['user2']; - - // Generate second ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_SUBJECT_ID, $attributes); - $value2 = $attributes[Constants::ATTR_SUBJECT_ID][0]; - - $this->assertNotSame($value1, $value2); - } - - - /** - * Test that generated ID's for the same user and same SP, but with a different scope are NOT equal - */ - public function testUniqueIdentifierDifferentScopes(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['user1'], 'scope' => ['example.org']], - ]; - - // Generate first ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_SUBJECT_ID, $attributes); - $value1 = $attributes[Constants::ATTR_SUBJECT_ID][0]; - - // Change the scope - $request['Attributes']['scope'] = ['example.edu']; - - // Generate second ID - $result = self::processFilter($config, $request); - $attributes = $result['Attributes']; - $this->assertArrayHasKey(Constants::ATTR_SUBJECT_ID, $attributes); - $value2 = $attributes[Constants::ATTR_SUBJECT_ID][0]; - - $this->assertNotSame($value1, $value2); - - $this->assertMatchesRegularExpression( - '/@example.org$/i', - $value1 - ); - $this->assertMatchesRegularExpression( - '/@example.edu$/i', - $value2 - ); - } - - - /** - * Test that weak identifiers log a warning - */ - public function testWeakIdentifierLogsWarning(): void - { - $config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope']; - $request = [ - 'Attributes' => ['uid' => ['a'], 'scope' => ['b']], - ]; - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('core:SubjectID: Generated ID \'a@b\' can hardly be considered globally unique.'); - - self::processFilter($config, $request); - } -}