diff --git a/modules/consent/docs/consent.md b/modules/consent/docs/consent.md index e503a5779fbea332c9a2c8b3a1a3452a818a5df8..58e27d913823605ec8097c2e4b632e3f1c8c1e03 100644 --- a/modules/consent/docs/consent.md +++ b/modules/consent/docs/consent.md @@ -232,6 +232,21 @@ Disable consent for some IdPs for a given SP: ), ), +### Regular expression support ### + +You can use regular expressions to evaluate the entityId of either the IdP +or the SP. It makes it possible to disable consent for an entire domain or +for a range of specific entityIds. Just use an array instead of a flat string +with the following format (note that flat string and array entries are allowed +at the same time) : + + $metadata['https://sp.example.org'] = array( + [...] + 'consent.disable' => array( + 'https://idp1.example.org/', + array('type'=>'regex', 'pattern'=>'/.*\.mycompany\.com.*/i'), + ), + ), Attribute presentation ---------------------- diff --git a/modules/consent/lib/Auth/Process/Consent.php b/modules/consent/lib/Auth/Process/Consent.php index 572bb3550fd3305cdce32c335ebab83b9b75a264..b0cc9a5e2870ab01d982b16851a2d124b5dbfcb1 100644 --- a/modules/consent/lib/Auth/Process/Consent.php +++ b/modules/consent/lib/Auth/Process/Consent.php @@ -10,7 +10,7 @@ class sspmod_consent_Auth_Process_Consent extends SimpleSAML_Auth_ProcessingFilter { /** - * Button to recive focus + * Button to receive focus * * @var string|null */ @@ -144,13 +144,55 @@ class sspmod_consent_Auth_Process_Consent extends SimpleSAML_Auth_ProcessingFilt /** * Helper function to check whether consent is disabled. * - * @param mixed $option The consent.disable option. Either an array or a boolean. + * @param mixed $option The consent.disable option. Either an array of array, an array or a boolean. * @param string $entityIdD The entityID of the SP/IdP. * @return boolean TRUE if disabled, FALSE if not. */ private static function checkDisable($option, $entityId) { if (is_array($option)) { - return in_array($entityId, $option, TRUE); + // Check if consent.disable array has one element that is an array + if (count($option) === count($option, COUNT_RECURSIVE)) { + // Array is not multidimensional. Simple in_array search suffices + return in_array($entityId, $option, true); + } + + // Array contains at least one element that is an array, verify both possibilities + if (in_array($entityId, $option, true)) { + return true; + } + + // Search in multidimensional arrays + foreach ($option as $optionToTest) { + if (!is_array($optionToTest)) { + continue; // bad option + } + + if (!array_key_exists('type', $optionToTest)) { + continue; // option has no type + } + + // Option has a type - switch processing depending on type value : + if ($optionToTest['type'] === 'regex') { + // regex-based consent disabling + + if (!array_key_exists('pattern', $optionToTest)) { + continue; // no pattern defined + } + + if (preg_match($optionToTest['pattern'], $entityId) === 1) { + return true; + } + + } else { + // option type is not supported + continue; + } + + } // end foreach + + // Base case : no match + return false; + } else { return (boolean)$option; } diff --git a/tests/modules/consent/lib/Auth/Process/ConsentTest.php b/tests/modules/consent/lib/Auth/Process/ConsentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fd17281c0e83ba7800af1005f50954fa3694bbec --- /dev/null +++ b/tests/modules/consent/lib/Auth/Process/ConsentTest.php @@ -0,0 +1,100 @@ +<?php +/** + * Test for the consent:Process filter. + * + * @author Vincent Rioux <vrioux@ctech.ca> + * @package SimpleSAMLphp + */ + +// Consent module has no namespace yet. We should add it and then add it here also +//namespace SimpleSAML\Test\Module\consent\Auth\Process; + + +class ConsentTest extends \PHPUnit_Framework_TestCase +{ + + /* + * 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 function processFilter(array $config, array $request) + { + $filter = new sspmod_consent_Auth_Process_Consent($config, null); + $filter->process($request); + return $request; + } + + + /** + * Test valid consent disable. + */ + public function testValidConsentDisable() + { + // test consent disable regex with match + $config = array(); + + // test consent disable with match on specific SP entityid + $request = array( + 'Source' => array( + 'entityid' => 'https://idp.example.org', + 'metadata-set' => 'saml20-idp-local', + 'consent.disable' => array( + 'https://valid.flatstring.example.that.does.not.match', + array(), // invalid consent option array should be ignored + array('type'=>'invalid'), // invalid consent option type should be ignored + array('type'=>'regex'), // regex consent option without pattern should be ignored + array('type'=>'regex', 'pattern'=>'/.*\.valid.regex\.that\.does\.not\.match.*/i'), + 'https://sp.example.org/my-sp', // accept the SP that has this specific entityid + ), + 'SingleSignOnService' => array( + array( + 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + 'Location' => 'https://idp.example.org/saml2/idp/SSOService.php', + ), + ), + ), + 'Destination' => array( + 'entityid' => 'https://sp.example.org/my-sp', // valid entityid equal to the last one in the consent.disable array + 'metadata-set' => 'saml20-sp-remote', + ), + 'UserID' => 'jdoe', + 'Attributes' => array( + 'eduPersonPrincipalName' => array('jdoe@example.com'), + ), + ); + $result = $this->processFilter($config, $request); + $this->assertEquals($request, $result); // The state should NOT have changed because NO consent should be necessary (match) + + // test consent disable with match on SP through regular expression + $request = array( + 'Source' => array( + 'entityid' => 'https://idp.example.org', + 'metadata-set' => 'saml20-idp-local', + 'consent.disable' => array( + array('type'=>'regex', 'pattern'=>'/.*\.example\.org.*/i'), // accept any SP that has an entityid that contains the string ".example.org" + ), + 'SingleSignOnService' => array( + array( + 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + 'Location' => 'https://idp.example.org/saml2/idp/SSOService.php', + ), + ), + ), + 'Destination' => array( + 'entityid' => 'https://sp.example.org/my-sp', // sp contains the string ".example.org" + 'metadata-set' => 'saml20-sp-remote', + ), + 'UserID' => 'jdoe', + 'Attributes' => array( + 'eduPersonPrincipalName' => array('jdoe@example.com'), + ), + ); + $result = $this->processFilter($config, $request); + $this->assertEquals($request, $result); // The state should NOT have changed because NO consent should be necessary (match) + + } + +}