Skip to content

Commit

Permalink
Implement saml:AttributeStatement
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed Feb 27, 2024
1 parent 2812fca commit b156b87
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 0 deletions.
92 changes: 92 additions & 0 deletions src/SAML11/XML/saml/AbstractAttributeStatementType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\saml;

use DateTimeImmutable;
use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Exception\ProtocolViolationException;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
use SimpleSAML\XML\Exception\MissingElementException;
use SimpleSAML\XML\Exception\SchemaViolationException;
use SimpleSAML\XML\Exception\TooManyElementsException;

/**
* SAML AttributeStatementType abstract data type.
*
* @package simplesamlphp/saml11
*/
abstract class AbstractAttributeStatementType extends AbstractSubjectStatementType
{
/**
* Initialize a saml:AttributeStatementType from scratch
*
* @param \SimpleSAML\SAML11\XML\saml\Subject $subject
* @param array<\SimpleSAML\SAML11\XML\saml\Attribute> $attribute
*/
public function __construct(
Subject $subject,
protected array $attribute = [],
) {
Assert::allIsInstanceOf($attribute, Attribute::class, SchemaViolationException::class);

parent::__construct($subject);
}


/**
* Collect the value of the attribute-property
*
* @return array<\SimpleSAML\SAML11\XML\saml\Attribute>
*/
public function getAttributes(): array
{
return $this->attribute;
}


/**
* Convert XML into an AttributeStatementType
*
* @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, static::getLocalName(), InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);

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

return new static(
array_pop($subject),
Attribute::getChildrenOfClass($xml),
);
}


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

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

return $e;
}
}
14 changes: 14 additions & 0 deletions src/SAML11/XML/saml/AttributeStatement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\saml;

/**
* Class representing a saml:AttributeStatement element.
*
* @package simplesamlphp/saml11
*/
final class AttributeStatement extends AbstractAttributeStatementType
{
}
22 changes: 22 additions & 0 deletions tests/resources/xml/saml_AttributeStatement.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<saml:AttributeStatement xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
<saml:Subject>
<saml:NameIdentifier NameQualifier="TheNameQualifier" Format="urn:the:format">TheNameIDValue</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>_Test1</saml:ConfirmationMethod>
<saml:ConfirmationMethod>_Test2</saml:ConfirmationMethod>
<saml:SubjectConfirmationData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:integer">2</saml:SubjectConfirmationData>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="fed654">
<ds:KeyName>testkey</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate>MIICxDCCAi2gAwIBAgIUZ9QDx+SBFHednUWDFGm9tyVKrgQwDQYJKoZIhvcNAQELBQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcGA1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNVBAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjIxMjAzMTAzNTQwWhgPMjEyMjExMDkxMDM1NDBaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5vcmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDessdFRVDTMQQW3Na81B1CjJV1tmY3nopoIhZrkbDxLa+pv7jGDRcYreyu1DoQxEs06V2nHLoyOPhqJXSFivqtUwVYhR6NYgbNI6RRSsIJCweH0YOdlHna7gULPcLX0Bfbi4odStaFwG9yzDySwSEPtsKxm5pENPjNVGh+jJ+H/QIDAQABo1MwUTAdBgNVHQ4EFgQUvV75t8EoQo2fVa0E9otdtIGK5X0wHwYDVR0jBBgwFoAUvV75t8EoQo2fVa0E9otdtIGK5X0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQANQUeiwPJXkWMXuaDHToEBKcezYGqGEYnGUi9LMjeb+Kln7X8nn5iknlz4k77rWCbSwLPC/WDr0ySYQA+HagaeUaFpoiYFJKS6uFlK1HYWnM3W4PUiGHg1/xeZlMO44wTwybXVo0y9KMhchfB5XNbDdoJcqWYvi6xtmZZNRbxUyw==</ds:X509Certificate>
<ds:X509SubjectName>/CN=selfsigned.simplesamlphp.org/O=SimpleSAMLphp HQ/L=Honolulu/ST=Hawaii/C=US</ds:X509SubjectName>
</ds:X509Data>
<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">some</ssp:Chunk>
</ds:KeyInfo>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" AttributeName="TheName" AttributeNamespace="https://example.org/">
<saml:AttributeValue>FirstValue</saml:AttributeValue>
<saml:AttributeValue>SecondValue</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
174 changes: 174 additions & 0 deletions tests/src/SAML11/XML/saml/AttributeStatementTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Test\SAML11\XML\saml;

use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Utils\XPath;
use SimpleSAML\SAML11\XML\saml\Attribute;
use SimpleSAML\SAML11\XML\saml\AttributeValue;
use SimpleSAML\SAML11\XML\saml\AttributeStatement;
use SimpleSAML\SAML11\XML\saml\ConfirmationMethod;
use SimpleSAML\SAML11\XML\saml\NameIdentifier;
use SimpleSAML\SAML11\XML\saml\Subject;
use SimpleSAML\SAML11\XML\saml\SubjectConfirmation;
use SimpleSAML\SAML11\XML\saml\SubjectConfirmationData;
use SimpleSAML\XML\Chunk;
use SimpleSAML\XML\DOMDocumentFactory;
use SimpleSAML\XML\TestUtils\SchemaValidationTestTrait;
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
use SimpleSAML\XMLSecurity\XML\ds\KeyName;
use SimpleSAML\XMLSecurity\XML\ds\X509Certificate;
use SimpleSAML\XMLSecurity\XML\ds\X509Data;
use SimpleSAML\XMLSecurity\XML\ds\X509SubjectName;

use function dirname;
use function strval;

/**
* Tests for AttributeStatement elements.
*
* @covers \SimpleSAML\SAML11\XML\saml\AttributeStatement
* @covers \SimpleSAML\SAML11\XML\saml\AbstractAttributeStatementType
* @covers \SimpleSAML\SAML11\XML\saml\AbstractSamlElement
*
* @package simplesamlphp/saml11
*/
final class AttributeStatementTest extends TestCase
{
use SchemaValidationTestTrait;
use SerializableElementTestTrait;

/** @var string */
private static string $certificate;

/** @var string[] */
private static array $certData;


/**
*/
public static function setUpBeforeClass(): void
{
self::$schemaFile = dirname(__FILE__, 6) . '/resources/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd';

self::$testedClass = AttributeStatement::class;

self::$xmlRepresentation = DOMDocumentFactory::fromFile(
dirname(__FILE__, 5) . '/resources/xml/saml_AttributeStatement.xml',
);

self::$certificate = str_replace(
[
'-----BEGIN CERTIFICATE-----',
'-----END CERTIFICATE-----',
'-----BEGIN RSA PUBLIC KEY-----',
'-----END RSA PUBLIC KEY-----',
"\r\n",
"\n",
],
[
'',
'',
'',
'',
"\n",
''
],
PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
);

self::$certData = openssl_x509_parse(
PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
);
}


// marshalling


/**
* Test creating an AttributeStatement from scratch
*/
public function testMarshalling(): void
{
$scd = new SubjectConfirmationData(2);

$keyInfo = new KeyInfo(
[
new KeyName('testkey'),
new X509Data(
[
new X509Certificate(self::$certificate),
new X509SubjectName(self::$certData['name']),
],
),
new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">some</ssp:Chunk>'
)->documentElement),
],
'fed654',
);

$sc = new SubjectConfirmation(
[new ConfirmationMethod('_Test1'), new ConfirmationMethod('_Test2')],
$scd,
$keyInfo,
);

$nameIdentifier = new NameIdentifier(
'TheNameIDValue',
'TheNameQualifier',
'urn:the:format',
);

$subject = new Subject($sc, $nameIdentifier);

$attribute = new Attribute(
'TheName',
'https://example.org/',
[new AttributeValue('FirstValue'), new AttributeValue('SecondValue')]
);

$attributeStatement = new AttributeStatement(
$subject,
[$attribute],
);

$this->assertEquals(
self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
strval($attributeStatement),
);
}


public function testMarshallingElementOrdering(): void
{
$attributeStatement = AttributeStatement::fromXML(self::$xmlRepresentation->documentElement);
$attributeStatementElement = $attributeStatement->toXML();

// Test for a Subject
$xpCache = XPath::getXPath($attributeStatementElement);
$attributeStatementElements = XPath::xpQuery(
$attributeStatementElement,
'./saml_assertion:Subject',
$xpCache,
);
$this->assertCount(1, $attributeStatementElements);

// Test ordering of AttributeStatement contents
/** @psalm-var \DOMElement[] $authnStatementElements */
$attributeStatementElements = XPath::xpQuery(
$attributeStatementElement,
'./saml_assertion:Subject/following-sibling::*',
$xpCache,
);
$this->assertCount(1, $attributeStatementElements);
$this->assertEquals('saml:Attribute', $attributeStatementElements[0]->tagName);
}
}

0 comments on commit b156b87

Please sign in to comment.