Skip to content

Commit

Permalink
Fix SubjectStatement implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed Mar 10, 2024
1 parent c7f16cb commit eee2e58
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 22 deletions.
6 changes: 3 additions & 3 deletions src/SAML11/XML/saml/AbstractAssertionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ public function getStatements(): array


/**
* @return \SimpleSAML\SAML11\XML\saml\SubjectStatement[]
* @return \SimpleSAML\SAML11\XML\saml\AbstractSubjectStatement[]
*/
public function getSubjectStatements(): array
{
return array_values(array_filter($this->statements, function ($statement) {
return $statement instanceof SubjectStatement;
return $statement instanceof AbstractSubjectStatement;
}));
}

Expand Down Expand Up @@ -256,7 +256,7 @@ public static function fromXML(DOMElement $xml): static
);

$statements = AbstractStatement::getChildrenOfClass($xml);
$subjectStatement = SubjectStatement::getChildrenOfClass($xml);
$subjectStatement = AbstractSubjectStatement::getChildrenOfClass($xml);
$authnStatement = AuthenticationStatement::getChildrenOfClass($xml);
$authzDecisionStatement = AuthorizationDecisionStatement::getChildrenOfClass($xml);
$attrStatement = AttributeStatement::getChildrenOfClass($xml);
Expand Down
131 changes: 131 additions & 0 deletions src/SAML11/XML/saml/AbstractSubjectStatement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\saml;

use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Utils;
use SimpleSAML\SAML11\XML\ExtensionPointInterface;
use SimpleSAML\SAML11\XML\ExtensionPointTrait;
use SimpleSAML\XML\Attribute as XMLAttribute;
use SimpleSAML\XML\Chunk;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
use SimpleSAML\XML\Exception\MissingElementException;
use SimpleSAML\XML\Exception\TooManyDOMElementsException;
use SimpleSAML\XML\Exception\SchemaViolationException;

use function count;
use function explode;

/**
* Class implementing the <saml:SubjectStatement> extension point.
*
* @package simplesamlphp/saml11
*/
abstract class AbstractSubjectStatement extends AbstractSubjectStatementType implements ExtensionPointInterface
{
use ExtensionPointTrait;

/** @var string */
public const LOCALNAME = 'SubjectStatement';


/**
* Initialize a custom saml:SubjectStatement element.
*
* @param string $type
*/
protected function __construct(
protected string $type,
Subject $subject,
) {
parent::__construct($subject);
}


/**
* @inheritDoc
*/
public function getXsiType(): string
{
return $this->type;
}


/**
* Convert an XML element into a SubjectStatement.
*
* @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, 'SubjectStatement', InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, C::NS_SAML, InvalidDOMElementException::class);
Assert::true(
$xml->hasAttributeNS(C::NS_XSI, 'type'),
'Missing required xsi:type in <saml:SubjectStatement> 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) {
$subject = Subject::getChildrenOfClass($xml);
Assert::minCount($subject, 1, MissingElementException::class);
Assert::maxCount($subject, 1, TooManyElementsException::class);

// we don't have a handler, proceed with unknown SubjectStatement
return new UnknownSubjectStatement(new Chunk($xml), $type, array_pop($subject));
}

Assert::subclassOf(
$handler,
AbstractSubjectStatement::class,
'Elements implementing SubjectStatement must extend \SimpleSAML\SAML11\XML\saml\AbstractSubjectStatementType.',
);
return $handler::fromXML($xml);
}


/**
* Convert this SubjectStatement to XML.
*
* @param \DOMElement $parent The element we are converting to XML.
* @return \DOMElement The XML element after adding the data corresponding to this SubjectStatement.
*/
public function toXML(DOMElement $parent = null): DOMElement
{
$e = parent::toXML($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;
}
}
14 changes: 0 additions & 14 deletions src/SAML11/XML/saml/SubjectStatement.php

This file was deleted.

51 changes: 51 additions & 0 deletions src/SAML11/XML/saml/UnknownSubjectStatement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\saml;

use DOMElement;
use SimpleSAML\XML\Chunk;

/**
* Class for unknown SubjectStatements.
*
* @package simplesamlphp/saml11
*/
final class UnknownSubjectStatement extends AbstractSubjectStatement
{
/**
* @param \SimpleSAML\XML\Chunk $chunk The whole SubjectStatement element as a chunk object.
* @param string $type The xsi:type of this SubjectStatement
*/
public function __construct(
protected Chunk $chunk,
string $type,
protected Subject $subject,
) {
parent::__construct($type, $subject);
}


/**
* Get the raw version of this SubjectStatement as a Chunk.
*
* @return \SimpleSAML\XML\Chunk
*/
public function getRawSubjectStatement(): Chunk
{
return $this->chunk;
}


/**
* Convert this unknown SubjectStatement to XML.
*
* @param \DOMElement|null $parent The element we are converting to XML.
* @return \DOMElement The XML element after adding the data corresponding to this unknown SubjectStatement.
*/
public function toXML(DOMElement $parent = null): DOMElement
{
return $this->getRawSubjectStatement()->toXML($parent);
}
}
15 changes: 15 additions & 0 deletions tests/resources/schemas/simplesamlphp.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@

<!-- End CustomStatement -->

<!-- Start CustomSubjectStatement -->

<element name="CustomSubjectStatement" type="ssp:CustomSubjectStatementType"/>
<complexType name="CustomSubjectStatementType">
<complexContent>
<extension base="saml:SubjectStatementAbstractType">
<sequence>
<element ref="saml:Audience" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>

<!-- End CustomSubjectStatement -->

<!-- Start CustomCondition -->

<element name="CustomCondition" type="ssp:CustomConditionType"/>
Expand Down
3 changes: 2 additions & 1 deletion tests/resources/xml/saml_SubjectStatement.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<saml:SubjectStatement xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
<saml:SubjectStatement xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ssp="urn:x-simplesamlphp:namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xsi:type="ssp:CustomSubjectStatementType">
<saml:Subject>
<saml:NameIdentifier NameQualifier="TheNameQualifier" Format="urn:the:format">TheNameIDValue</saml:NameIdentifier>
<saml:SubjectConfirmation>
Expand All @@ -15,4 +15,5 @@
</ds:KeyInfo>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Audience>urn:x-simplesamlphp:audience</saml:Audience>
</saml:SubjectStatement>
108 changes: 108 additions & 0 deletions tests/src/SAML11/CustomSubjectStatement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Test\SAML11;

use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\XML\saml\AbstractSubjectStatement;
use SimpleSAML\SAML11\XML\saml\Audience;
use SimpleSAML\SAML11\XML\saml\Subject;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\XML\Exception\InvalidDOMElementException;

/**
* Example class to demonstrate how SubjectStatement can be extended.
*
* @package simplesamlphp\saml11
*/
final class CustomSubjectStatement extends AbstractSubjectStatement
{
/** @var string */
protected const XSI_TYPE_NAME = 'CustomSubjectStatementType';

/** @var string */
protected const XSI_TYPE_NAMESPACE = 'urn:x-simplesamlphp:namespace';

/** @var string */
protected const XSI_TYPE_PREFIX = 'ssp';


/**
* CustomSubjectStatement constructor.
*
* @param \SimpleSAML\SAML11\XML\saml\Subject $subject
* @param \SimpleSAML\SAML11\XML\saml\Audience[] $audience
*/
public function __construct(
Subject $subject,
protected array $audience,
) {
Assert::allIsInstanceOf($audience, Audience::class);

parent::__construct(self::XSI_TYPE_PREFIX . ':' . self::XSI_TYPE_NAME, $subject);
}


/**
* Get the value of the audience-attribute.
*
* @return \SimpleSAML\SAML11\XML\saml\Audience[]
*/
public function getAudience(): array
{
return $this->audience;
}


/**
* Convert XML into an SubjectStatement
*
* @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, 'SubjectStatement', InvalidDOMElementException::class);
Assert::notNull($xml->namespaceURI, InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, AbstractSubjectStatement::NS, InvalidDOMElementException::class);
Assert::true(
$xml->hasAttributeNS(C::NS_XSI, 'type'),
'Missing required xsi:type in <saml:SubjectStatement> element.',
InvalidDOMElementException::class,
);

$type = $xml->getAttributeNS(C::NS_XSI, 'type');
Assert::same($type, self::XSI_TYPE_PREFIX . ':' . self::XSI_TYPE_NAME);

$subject = Subject::getChildrenOfClass($xml);
Assert::minCount($subject, 1, MissingElementException::class);
Assert::maxCount($subject, 1, TooManyElementsException::class);

$audience = Audience::getChildrenOfClass($xml);

return new static(array_pop($subject), $audience);
}


/**
* Convert this SubjectStatement to XML.
*
* @param \DOMElement $parent The element we are converting to XML.
* @return \DOMElement The XML element after adding the data corresponding to this SubjectStatement.
*/
public function toXML(DOMElement $parent = null): DOMElement
{
$e = parent::toXML($parent);

foreach ($this->audience as $audience) {
$audience->toXML($e);
}

return $e;
}
}
1 change: 1 addition & 0 deletions tests/src/SAML11/XML/saml/AssertionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use SimpleSAML\SAML11\Compat\ContainerSingleton;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\XML\saml\AbstractStatement;
use SimpleSAML\SAML11\XML\saml\AbstractSubjectStatement;
use SimpleSAML\SAML11\XML\saml\Advice;
use SimpleSAML\SAML11\XML\saml\Assertion;
use SimpleSAML\SAML11\XML\saml\AttributeStatement;
Expand Down
Loading

0 comments on commit eee2e58

Please sign in to comment.