Skip to content

Commit

Permalink
Migrate saml-specific assertions to this library
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed Aug 1, 2024
1 parent e79e91c commit a2303c3
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 9 deletions.
171 changes: 171 additions & 0 deletions src/SAML11/Assert/Assert.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\Assert;

use BadMethodCallException; // Requires ext-spl
use DateTime; // requires ext-date
use DateTimeImmutable; // requires ext-date
use InvalidArgumentException; // Requires ext-spl
use SimpleSAML\Assert\Assert as BaseAssert;
use SimpleSAML\Assert\AssertionFailedException;
use Throwable;

use function array_pop;
use function array_unshift;
use function call_user_func_array;
use function end;
use function is_object;
use function is_resource;
use function is_string;
use function is_subclass_of;
use function lcfirst;
use function method_exists;
use function preg_match; // Requires ext-pcre
use function strval;

/**
* SimpleSAML\SAML11\Assert\Assert wrapper class
*
* @package simplesamlphp/saml11
*
* @method static void validDateTime(mixed $value, string $message = '', string $exception = '')
* @method static void validURI(mixed $value, string $message = '', string $exception = '')
* @method static void validEntityID(mixed $value, string $message = '', string $exception = '')
* @method static void nullOrValidDateTime(mixed $value, string $message = '', string $exception = '')
* @method static void nullOrValidURI(mixed $value, string $message = '', string $exception = '')
* @method static void nullOrValidEntityID(mixed $value, string $message = '', string $exception = '')
* @method static void allValidDateTime(mixed $value, string $message = '', string $exception = '')
* @method static void allValidURI(mixed $value, string $message = '', string $exception = '')
* @method static void allValidEntityID(mixed $value, string $message = '', string $exception = '')
*/
final class Assert
{
use CustomAssertionTrait;


/**
* @param string $name
* @param array<mixed> $arguments
*/
public static function __callStatic(string $name, array $arguments): void
{
// Handle Exception-parameter
$exception = AssertionFailedException::class;

$last = end($arguments);
if (is_string($last) && class_exists($last) && is_subclass_of($last, Throwable::class)) {
$exception = $last;
array_pop($arguments);
}

try {
if (method_exists(static::class, $name)) {
call_user_func_array([static::class, $name], $arguments);
return;
} elseif (preg_match('/^nullOr(.*)$/i', $name, $matches)) {
$method = lcfirst($matches[1]);
if (method_exists(static::class, $method)) {
call_user_func_array([static::class, 'nullOr'], [[static::class, $method], $arguments]);
} elseif (method_exists(BaseAssert::class, $method)) {
call_user_func_array([static::class, 'nullOr'], [[BaseAssert::class, $method], $arguments]);
} else {
throw new BadMethodCallException(sprintf("Assertion named `%s` does not exists.", $method));
}
} elseif (preg_match('/^all(.*)$/i', $name, $matches)) {
$method = lcfirst($matches[1]);
if (method_exists(static::class, $method)) {
call_user_func_array([static::class, 'all'], [[static::class, $method], $arguments]);
} elseif (method_exists(BaseAssert::class, $method)) {
call_user_func_array([static::class, 'all'], [[BaseAssert::class, $method], $arguments]);
} else {
throw new BadMethodCallException(sprintf("Assertion named `%s` does not exists.", $method));
}
} else {
throw new BadMethodCallException(sprintf("Assertion named `%s` does not exists.", $name));
}
} catch (InvalidArgumentException $e) {
throw new $exception($e->getMessage());
}
}


/**
* Handle nullOr* for either Webmozart or for our custom assertions
*
* @param callable $method
* @param array<mixed> $arguments
* @return void
*/
private static function nullOr(callable $method, array $arguments): void
{
$value = reset($arguments);
($value === null) || call_user_func_array($method, $arguments);
}


/**
* all* for our custom assertions
*
* @param callable $method
* @param array<mixed> $arguments
* @return void
*/
private static function all(callable $method, array $arguments): void
{
$values = array_pop($arguments);
foreach ($values as $value) {
$tmp = $arguments;
array_unshift($tmp, $value);
call_user_func_array($method, $tmp);
}
}


/**
* @param mixed $value
*
* @return string
*/
protected static function valueToString(mixed $value): string
{
if (is_resource($value)) {
return 'resource';
}

if (null === $value) {
return 'null';
}

if (true === $value) {
return 'true';
}

if (false === $value) {
return 'false';
}

if (is_array($value)) {
return 'array';
}

if (is_object($value)) {
if (method_exists($value, '__toString')) {
return $value::class . ': ' . self::valueToString($value->__toString());
}

if ($value instanceof DateTime || $value instanceof DateTimeImmutable) {
return $value::class . ': ' . self::valueToString($value->format('c'));
}

return $value::class;
}

if (is_string($value)) {
return '"' . $value . '"';
}

return strval($value);
}
}
71 changes: 71 additions & 0 deletions src/SAML11/Assert/CustomAssertionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\Assert;

use SimpleSAML\Assert\Assert as BaseAssert;
use SimpleSAML\Assert\AssertionFailedException;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Exception\ProtocolViolationException;
use SimpleSAML\XML\Exception\SchemaViolationException;

/**
* @package simplesamlphp/assert
*/
trait CustomAssertionTrait
{
private static string $scheme_regex = '/^([a-z][a-z0-9\+\-\.]+[:])/i';

/***********************************************************************************
* NOTE: Custom assertions may be added below this line. *
* They SHOULD be marked as `private` to ensure the call is forced *
* through __callStatic(). *
* Assertions marked `public` are called directly and will *
* not handle any custom exception passed to it. *
***********************************************************************************/


/**
* @param string $value
* @param string $message
*/
private static function validDateTime(string $value, string $message = ''): void
{
try {
BaseAssert::validDateTime($value, $message);
} catch (AssertionFailedException $e) {
throw new SchemaViolationException($e->getMessage());
}

try {
BaseAssert::endsWith(
$value,
'Z',
$message ?: '%s is not a DateTime expressed in the UTC timezone using the \'Z\' timezone identifier.',
);
} catch (AssertionFailedException $e) {
throw new ProtocolViolationException($e->getMessage());
}
}


/**
* @param string $value
* @param string $message
*/
private static function validURI(string $value, string $message = ''): void
{
try {
BaseAssert::validURI($value, $message);
} catch (AssertionFailedException $e) {
throw new SchemaViolationException($e->getMessage());
}

try {
BaseAssert::notWhitespaceOnly($value, $message ?: '%s is not a SAML1.1-compliant URI');
} catch (AssertionFailedException $e) {
throw new ProtocolViolationException($e->getMessage());
}
}
}
3 changes: 2 additions & 1 deletion src/SAML11/XML/ExtensionPointTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use RuntimeException;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Assert\Assert as SAMLAssert;
use SimpleSAML\XML\Exception\SchemaViolationException;

/**
Expand Down Expand Up @@ -48,7 +49,7 @@ public static function getXsiTypeNamespaceURI(): string
RuntimeException::class,
);

Assert::validURI(static::XSI_TYPE_NAMESPACE, SchemaViolationException::class);
SAMLAssert::validURI(static::XSI_TYPE_NAMESPACE, SchemaViolationException::class);
return static::XSI_TYPE_NAMESPACE;
}

Expand Down
3 changes: 2 additions & 1 deletion src/SAML11/XML/saml/AbstractAssertionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTimeImmutable;
use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Assert\Assert as SAMLAssert;
use SimpleSAML\SAML11\Compat\ContainerSingleton;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Exception\ProtocolViolationException;
Expand Down Expand Up @@ -248,7 +249,7 @@ public static function fromXML(DOMElement $xml): static
// Strip sub-seconds - See paragraph 1.2.2 of SAML core specifications
$issueInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $issueInstant, 1);

Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
SAMLAssert::validDateTime($issueInstant, ProtocolViolationException::class);
$issueInstant = new DateTimeImmutable($issueInstant);

$conditions = Conditions::getChildrenOfClass($xml);
Expand Down
5 changes: 3 additions & 2 deletions src/SAML11/XML/saml/AbstractAuthenticationStatementType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTimeImmutable;
use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Assert\Assert as SAMLAssert;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Exception\ProtocolViolationException;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
Expand Down Expand Up @@ -37,7 +38,7 @@ final public function __construct(
protected ?SubjectLocality $subjectLocality = null,
protected array $authorityBinding = [],
) {
Assert::validURI($authenticationMethod);
SAMLAssert::validURI($authenticationMethod);
Assert::allIsInstanceOf($authorityBinding, AuthorityBinding::class, SchemaViolationException::class);

parent::__construct($subject);
Expand Down Expand Up @@ -106,7 +107,7 @@ public static function fromXML(DOMElement $xml): static
// 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);
SAMLAssert::validDateTime($authenticationInstant, ProtocolViolationException::class);
$authenticationInstant = new DateTimeImmutable($authenticationInstant);

$authorityBinding = AuthorityBinding::getChildrenOfClass($xml);
Expand Down
5 changes: 3 additions & 2 deletions src/SAML11/XML/saml/AbstractAuthorityBindingType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Assert\Assert as SAMLAssert;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\XML\Exception\InvalidDOMElementException;

Expand All @@ -29,8 +30,8 @@ final public function __construct(
protected string $Binding,
) {
Assert::validQName($AuthorityKind);
Assert::validURI($Location);
Assert::validURI($Binding);
SAMLAssert::validURI($Location);
SAMLAssert::validURI($Binding);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Assert\Assert as SAMLAssert;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
use SimpleSAML\XML\Exception\MissingElementException;
use SimpleSAML\XML\Exception\SchemaViolationException;
Expand Down Expand Up @@ -34,7 +35,7 @@ final public function __construct(
protected array $action = [],
protected ?Evidence $evidence = null,
) {
Assert::validURI($resource);
SAMLAssert::validURI($resource);
Assert::minCount($action, 1, MissingElementException::class);
Assert::allIsInstanceOf($action, Action::class, SchemaViolationException::class);

Expand Down
5 changes: 3 additions & 2 deletions src/SAML11/XML/saml/AbstractConditionsType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTimeImmutable;
use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Assert\Assert as SAMLAssert;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Exception\ProtocolViolationException;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
Expand Down Expand Up @@ -128,14 +129,14 @@ public static function fromXML(DOMElement $xml): static
// Strip sub-seconds - See paragraph 1.2.2 of SAML core specifications
$notBefore = preg_replace('/([.][0-9]+Z)$/', 'Z', $notBefore, 1);

Assert::validDateTimeZulu($notBefore, ProtocolViolationException::class);
SAMLAssert::validDateTime($notBefore, ProtocolViolationException::class);
$notBefore = new DateTimeImmutable($notBefore);

$notOnOrAfter = self::getOptionalAttribute($xml, 'NotOnOrAfter');
// Strip sub-seconds - See paragraph 1.2.2 of SAML core specifications
$notOnOrAfter = preg_replace('/([.][0-9]+Z)$/', 'Z', $notOnOrAfter, 1);

Assert::validDateTimeZulu($notOnOrAfter, ProtocolViolationException::class);
SAMLAssert::validDateTime($notOnOrAfter, ProtocolViolationException::class);
$notOnOrAfter = new DateTimeImmutable($notOnOrAfter);

$audienceRestrictionCondition = AudienceRestrictionCondition::getChildrenOfClass($xml);
Expand Down
Loading

0 comments on commit a2303c3

Please sign in to comment.