Skip to content

Commit

Permalink
Add samlp:Status
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed Nov 19, 2024
1 parent a046c96 commit 86830a6
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 0 deletions.
136 changes: 136 additions & 0 deletions src/SAML11/XML/samlp/AbstractStatusType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\samlp;

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\TooManyElementsException;

use function array_pop;

/**
* SAML Status data type.
*
* @package simplesamlphp/saml11
*/
abstract class AbstractStatusType extends AbstractSamlpElement
{
/**
* Initialize a samlp:Status
*
* @param \SimpleSAML\SAML11\XML\samlp\StatusCode $statusCode
* @param \SimpleSAML\SAML11\XML\samlp\StatusMessage|null $statusMessage
* @param \SimpleSAML\SAML11\XML\samlp\StatusDetail|null $statusDetail
*/
final public function __construct(
protected StatusCode $statusCode,
protected ?StatusMessage $statusMessage = null,
protected ?StatusDetail $statusDetail = null,
) {
Assert::oneOf(
$statusCode->getValue(),
[
C::STATUS_SUCCESS,
C::STATUS_REQUESTER,
C::STATUS_RESPONDER,
C::STATUS_VERSION_MISMATCH,
],
'Invalid top-level status code: %s',
ProtocolViolationException::class,
);
}


/**
* Collect the StatusCode
*
* @return \SimpleSAML\SAML11\XML\samlp\StatusCode
*/
public function getStatusCode(): StatusCode
{
return $this->statusCode;
}


/**
* Collect the value of the statusMessage
*
* @return \SimpleSAML\SAML11\XML\samlp\StatusMessage|null
*/
public function getStatusMessage(): ?StatusMessage
{
return $this->statusMessage;
}


/**
* Collect the value of the statusDetails property
*
* @return \SimpleSAML\SAML11\XML\samlp\StatusDetail|null
*/
public function getStatusDetail(): ?StatusDetail
{
return $this->statusDetail;
}


/**
* Convert XML into a Status
*
* @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
* @throws \SimpleSAML\XML\Exception\TooManyElementsException
* if too many child-elements of a type are specified
* @throws \SimpleSAML\XML\Exception\MissingElementException
* if one of the mandatory child-elements is missing
*/
public static function fromXML(DOMElement $xml): static
{
Assert::same($xml->localName, 'Status', InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, Status::NS, InvalidDOMElementException::class);

$statusCode = StatusCode::getChildrenOfClass($xml);
Assert::minCount($statusCode, 1, MissingElementException::class);
Assert::count($statusCode, 1, TooManyElementsException::class);

$statusMessage = StatusMessage::getChildrenOfClass($xml);
Assert::maxCount($statusMessage, 1, TooManyElementsException::class);

$statusDetail = StatusDetail::getChildrenOfClass($xml);

return new static(
array_pop($statusCode),
array_pop($statusMessage),
array_pop($statusDetail),
);
}


/**
* Convert this Status 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 Status.
*/
public function toXML(DOMElement $parent = null): DOMElement
{
$e = $this->instantiateParentElement($parent);

$this->getStatusCode()->toXML($e);

$this->getStatusMessage()?->toXML($e);

$this->getStatusDetail()?->toXML($e);

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

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\samlp;

/**
* Class representing a samlp:Status element.
*
* @package simplesaml/saml11
*/
final class Status extends AbstractStatusType
{
}
9 changes: 9 additions & 0 deletions tests/resources/xml/samlp_Status.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<samlp:Status xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol">
<samlp:StatusCode Value="samlp:Responder">
<samlp:StatusCode Value="samlp:RequestDenied"/>
</samlp:StatusCode>
<samlp:StatusMessage>Something went wrong</samlp:StatusMessage>
<samlp:StatusDetail>
<ssp:Cause xmlns:ssp="urn:custom:ssp">org.sourceid.websso.profiles.idp.FailedAuthnSsoException</ssp:Cause>
</samlp:StatusDetail>
</samlp:Status>
120 changes: 120 additions & 0 deletions tests/src/SAML11/XML/samlp/StatusTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Test\SAML11\XML\samlp;

use DOMDocument;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Utils\XPath;
use SimpleSAML\SAML11\XML\samlp\AbstractSamlpElement;
use SimpleSAML\SAML11\XML\samlp\Status;
use SimpleSAML\SAML11\XML\samlp\StatusCode;
use SimpleSAML\SAML11\XML\samlp\StatusDetail;
use SimpleSAML\SAML11\XML\samlp\StatusMessage;
use SimpleSAML\XML\Chunk;
use SimpleSAML\XML\DOMDocumentFactory;
use SimpleSAML\XML\TestUtils\SchemaValidationTestTrait;
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;

use function dirname;
use function strval;

/**
* Class \SimpleSAML\SAML11\XML\samlp\StatusTest
*
* @package simplesamlphp/saml11
*/
#[Group('samlp')]
#[CoversClass(Status::class)]
#[CoversClass(AbstractSamlpElement::class)]
final class StatusTest extends TestCase
{
use SchemaValidationTestTrait;
use SerializableElementTestTrait;

/** @var \DOMDocument $detail */
private static DOMDocument $detail;


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

self::$testedClass = Status::class;

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

self::$detail = DOMDocumentFactory::fromFile(
dirname(__FILE__, 5) . '/resources/xml/samlp_StatusDetail.xml',
);
}


/**
*/
public function testMarshalling(): void
{
$status = new Status(
new StatusCode(
C::STATUS_RESPONDER,
[
new StatusCode(
C::STATUS_REQUEST_DENIED,
),
],
),
new StatusMessage('Something went wrong'),
StatusDetail::fromXML(
DOMDocumentFactory::fromFile(
dirname(__FILE__, 5) . '/resources/xml/samlp_StatusDetail.xml',
)->documentElement,
),
);

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


/**
*/
public function testMarshallingElementOrdering(): void
{
$status = new Status(
new StatusCode(
C::STATUS_RESPONDER,
[
new StatusCode(
C::STATUS_REQUEST_DENIED,
),
],
),
new StatusMessage('Something went wrong'),
new StatusDetail([new Chunk(self::$detail->documentElement)]),
);

$statusElement = $status->toXML();

// Test for a StatusCode
$xpCache = XPath::getXPath($statusElement);
$statusElements = XPath::xpQuery($statusElement, './saml_protocol:StatusCode', $xpCache);
$this->assertCount(1, $statusElements);

// Test ordering of Status contents
/** @psalm-var \DOMElement[] $statusElements */
$statusElements = XPath::xpQuery($statusElement, './saml_protocol:StatusCode/following-sibling::*', $xpCache);
$this->assertCount(2, $statusElements);
$this->assertEquals('samlp:StatusMessage', $statusElements[0]->tagName);
$this->assertEquals('samlp:StatusDetail', $statusElements[1]->tagName);
}
}

0 comments on commit 86830a6

Please sign in to comment.