Skip to content
Snippets Groups Projects
Commit 9e954f4a authored by Tim van Dijen's avatar Tim van Dijen
Browse files

Import subjectidattrs module

parent 0ffa7021
No related branches found
No related tags found
No related merge requests found
`subjectidattrs:PairwiseID`
===================
Filter to insert a pairwise-id that complies with the
[SAML V2.0 Subject Identifier Attributes Profile][specification].
[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
--------
```php
'authproc' => [
50 => [
'class' => 'subjectidattrs:PairwiseID',
'identifyingAttribute' => 'uid',
'scopeAttribute' => 'scope',
],
],
```
`subjectidattrs:SubjectID`
===================
Filter to insert a subject-id that complies with the
[SAML V2.0 Subject Identifier Attributes Profile][specification].
[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
--------
```php
'authproc' => [
50 => [
'class' => 'subjectidattrs:SubjectID',
'identifyingAttribute' => 'uid',
'scopeAttribute' => 'scope',
],
],
```
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\saml\Auth\Process;
use SAML2\Constants;
use SimpleSAML\Assert\Assert;
use SimpleSAML\{Auth, 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 => [
* 'saml: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 Utils\Config $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_hmac('sha256', $userID . '|' . $sp_entityid, $salt, false);
$value = $hash . '@' . strtolower($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;
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\saml\Auth\Process;
use SAML2\Constants;
use SAML2\Exception\ProtocolViolationException;
use SimpleSAML\Assert\Assert;
use SimpleSAML\{Auth, 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 => [
* 'saml: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,}@[a-z0-9][a-z0-9.-]+\.[a-z]{2,}$/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 'scopeAttribute' 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(
"saml:" . 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, 'saml:' . 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 scope is an empty string
* @throws \SAML2\Exception\ProtocolViolationException 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(
"saml:" . 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, 'saml:' . 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,
'saml:' . 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 \SAML2\Exception\ProtocolViolationException if the post-conditions are not met
*/
protected function validateGeneratedIdentifier(string $value): void
{
Assert::regex(
$value,
self::SPEC_PATTERN,
'saml:' . static::NAME . ': Generated ID \'' . $value . '\' contains illegal characters.',
ProtocolViolationException::class
);
if (preg_match(self::WARN_PATTERN, $value) === 0) {
$this->logger::warning(
'saml:' . 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;
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Test\Module\saml\Auth\Process;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use SAML2\Constants;
use SAML2\Exception\ProtocolViolationException;
use SimpleSAML\Assert\AssertionFailedException;
use SimpleSAML\{Configuration, Logger, Utils};
use SimpleSAML\Module\saml\Auth\Process\PairwiseID;
/**
* Test for the saml:PairwiseID filter.
*
* @covers \SimpleSAML\Module\saml\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(
'c22d58bebef42e50e203d0e932ae4a7f560a51d494266990a5b5c73f34b1854e@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(
'c22d58bebef42e50e203d0e932ae4a7f560a51d494266990a5b5c73f34b1854e@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(
'c22d58bebef42e50e203d0e932ae4a7f560a51d494266990a5b5c73f34b1854e@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(
'c22d58bebef42e50e203d0e932ae4a7f560a51d494266990a5b5c73f34b1854e@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(ProtocolViolationException::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',
];
$expected = 'be511fc7f95e22816dbac21e3b70546660963b6e9b85f5a41d80bfc6baadd547@b';
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage(
'saml:PairwiseID: Generated ID \'' . $expected . '\' can hardly be considered globally unique.'
);
self::processFilter($config, $request);
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Test\Module\saml\Auth\Process;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use SAML2\Constants;
use SAML2\Exception\ProtocolViolationException;
use SimpleSAML\Assert\AssertionFailedException;
use SimpleSAML\{Configuration, Logger, Utils};
use SimpleSAML\Module\saml\Auth\Process\SubjectID;
/**
* Test for the saml:SubjectID filter.
*
* @covers \SimpleSAML\Module\saml\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(ProtocolViolationException::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(ProtocolViolationException::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('saml:SubjectID: Generated ID \'a@b\' can hardly be considered globally unique.');
self::processFilter($config, $request);
}
/**
* Test that weak identifiers log a warning: not an actual domain name
*/
public function testScopeNotADomainLogsWarning(): void
{
$config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope'];
$request = [
'Attributes' => ['uid' => ['a1398u9u25'], 'scope' => ['example']],
];
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('saml:SubjectID: Generated ID \'a1398u9u25@example\' can hardly be considered globally unique.');
self::processFilter($config, $request);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment