Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement saml:AuthzDecisionStatement element #322

Merged
merged 1 commit into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/SAML2/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -453,4 +453,17 @@ class Constants extends \SimpleSAML\XMLSecurity\Constants
* The maximum size for any entityid as per SAML2INT-specification
*/
public const SAML2INT_ENTITYID_MAX_LENGTH = 256;

/**
* Valid values for saml:DecisionType
*/
public const AUTHZ_DECISION_PERMIT = 'Permit';
public const AUTHZ_DECISION_DENY = 'Deny';
public const AUTHZ_DECISION_INDETERMINATE = 'Indeterminate';

public const AUTHZ_DECISIONS = [
self::AUTHZ_DECISION_PERMIT,
self::AUTHZ_DECISION_DENY,
self::AUTHZ_DECISION_INDETERMINATE,
];
}
156 changes: 156 additions & 0 deletions src/SAML2/XML/saml/AuthzDecisionStatement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML2\XML\saml;

use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML2\Constants as C;
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
use SimpleSAML\XML\Exception\MissingElementException;
use SimpleSAML\XML\Exception\SchemaViolationException;
use SimpleSAML\XML\Exception\TooManyElementsException;
use SimpleSAML\XML\Utils as XMLUtils;

use function array_pop;
use function gmdate;

/**
* Class representing a SAML2 AuthzDecisionStatement
*
* @package simplesamlphp/saml2
*/
final class AuthzDecisionStatement extends AbstractStatementType
{
/**
* Initialize an AuthzDecisionStatement.
*
* @param string $resource
* @param string $decision
* @param \SimpleSAML\SAML2\XML\saml\Action[] $action
* @param \SimpleSAML\SAML2\XML\saml\Evidence|null
*/
public function __construct(
protected string $resource,
protected string $decision,
protected array $action,
protected ?Evidence $evidence = null,
) {
Assert::validURI($resource);
Assert::oneOf($decision, C::AUTHZ_DECISIONS, ProtocolViolationException::class);
Assert::allIsInstanceOf($action, Action::class, SchemaViolationException::class);
}


/**
* Collect the value of the resource-property
*
* @return string
*/
public function getResource(): string
{
return $this->resource;
}


/**
* Collect the value of the decision-property
*
* @return string
*/
public function getDecision(): string
{
return $this->decision;
}


/**
* Collect the value of the action-property
*
* @return array
*/
public function getAction(): array
{
return $this->action;
}


/**
* Collect the value of the evidence-property
*
* @return \SimpleSAML\SAML2\XML\saml\Evidence|null
*/
public function getEvidence(): ?Evidence
{
return $this->evidence;
}


/**
* Convert XML into an AuthzDecisionStatement
*
* @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\MissingElementException
* if one of the mandatory child-elements is missing
* @throws \Exception if the authentication instant is not a valid timestamp.
*/
public static function fromXML(DOMElement $xml): static
{
Assert::same($xml->localName, 'AuthzDecisionStatement', InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, AuthzDecisionStatement::NS, InvalidDOMElementException::class);

$action = Action::getChildrenOfClass($xml);
Assert::minCount(
$action,
1,
'Missing <saml:Action> in <saml:AuthzDecisionStatement>',
MissingElementException::class,
);

$evidence = Evidence::getChildrenOfClass($xml);
Assert::maxCount(
$evidence,
1,
'Too many <saml:Evidence> in <saml:AuthzDecisionStatement>',
TooManyElementException::class,
);

return new static(
self::getAttribute($xml, 'Resource'),
self::getAttribute($xml, 'Decision'),
$action,
array_pop($evidence),
);
}


/**
* Convert this AuthzDecisionStatement to XML.
*
* @param \DOMElement|null $parent The element we should append this AuthzDecisionStatement to.
* @return \DOMElement
*/
public function toXML(DOMElement $parent = null): DOMElement
{
$e = $this->instantiateParentElement($parent);

$e->setAttribute('Resource', $this->getResource());
$e->setAttribute('Decision', $this->getDecision());

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

if ($this->getEvidence() !== null && !$this->getEvidence()->isEmptyElement()) {
$this->getEvidence()->toXML($e);
}

return $e;
}
}
87 changes: 87 additions & 0 deletions tests/SAML2/XML/saml/AuthzDecisionStatementTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML2\Test\SAML2\XML\saml;

use DOMDocument;
use PHPUnit\Framework\TestCase;
use SimpleSAML\SAML2\XML\saml\Action;
use SimpleSAML\SAML2\XML\saml\AuthzDecisionStatement;
use SimpleSAML\SAML2\XML\saml\Evidence;
use SimpleSAML\Test\XML\SchemaValidationTestTrait;
use SimpleSAML\Test\XML\SerializableElementTestTrait;
use SimpleSAML\XML\DOMDocumentFactory;

use function dirname;
use function strval;

/**
* Class \SimpleSAML\SAML2\XML\saml\AuthzDecisionStatementTest
*
* @covers \SimpleSAML\SAML2\XML\saml\AuthzDecisionStatement
* @covers \SimpleSAML\SAML2\XML\saml\AbstractStatement
* @covers \SimpleSAML\SAML2\XML\saml\AbstractSamlElement
*
* @package simplesamlphp/saml2
*/
final class AuthzDecisionStatementTest extends TestCase
{
use SchemaValidationTestTrait;
use SerializableElementTestTrait;

/** @var \DOMDocument $evidence */
private DOMDocument $evidence;


/**
*/
protected function setUp(): void
{
$this->schema = dirname(__FILE__, 5) . '/schemas/saml-schema-assertion-2.0.xsd';

$this->testedClass = AuthzDecisionStatement::class;

$this->xmlRepresentation = DOMDocumentFactory::fromFile(
dirname(__FILE__, 4) . '/resources/xml/saml_AuthzDecisionStatement.xml',
);

$this->evidence = DOMDocumentFactory::fromFile(
dirname(__FILE__, 4) . '/resources/xml/saml_Evidence.xml',
);
}


/**
*/
public function testMarshalling(): void
{
$authzDecisionStatement = new AuthzDecisionStatement(
'urn:x-simplesamlphp:resource',
'Permit',
[
new Action('urn:x-simplesamlphp:namespace', 'SomeAction'),
new Action('urn:x-simplesamlphp:namespace', 'OtherAction'),
],
Evidence::fromXML($this->evidence->documentElement),
);

$this->assertEquals(
$this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement),
strval($authzDecisionStatement),
);
}


/**
*/
public function testUnmarshalling(): void
{
$authzDecisionStatement = AuthzDecisionStatement::fromXML($this->xmlRepresentation->documentElement);

$this->assertEquals(
$this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement),
strval($authzDecisionStatement),
);
}
}
63 changes: 63 additions & 0 deletions tests/resources/xml/saml_AuthzDecisionStatement.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<saml:AuthzDecisionStatement xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Resource="urn:x-simplesamlphp:resource" Decision="Permit">
<saml:Action Namespace="urn:x-simplesamlphp:namespace">SomeAction</saml:Action>
<saml:Action Namespace="urn:x-simplesamlphp:namespace">OtherAction</saml:Action>
<saml:Evidence>
<saml:AssertionIDRef>_Test</saml:AssertionIDRef>
<saml:AssertionURIRef>urn:x-simplesamlphp:reference</saml:AssertionURIRef>
<saml:Assertion Version="2.0" ID="_93af655219464fb403b34436cfb0c5cb1d9a5502" IssueInstant="1970-01-01T01:33:31Z">
<saml:Issuer>Provider</saml:Issuer>
<saml:Subject>
<saml:NameID SPNameQualifier="https://sp.example.org/authentication/sp/metadata" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">SomeNameIDValue</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:NameID SPNameQualifier="https://sp.example.org/authentication/sp/metadata" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">SomeOtherNameIDValue</saml:NameID>
<saml:SubjectConfirmationData NotOnOrAfter="2011-08-31T08:51:05Z" Recipient="https://sp.example.org/authentication/sp/consume-assertion" InResponseTo="_13603a6565a69297e9809175b052d115965121c8" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2011-08-31T08:51:05Z" NotOnOrAfter="2011-08-31T08:51:05Z">
<saml:AudienceRestriction>
<saml:Audience>https://simplesamlphp.org/sp/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2011-08-31T08:51:05Z" SessionIndex="_93af655219464fb403b34436cfb0c5cb1d9a5502">
<saml:SubjectLocality Address="127.0.0.1"/>
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="urn:test:ServiceID">
<saml:AttributeValue>1</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="urn:test:EntityConcernedID">
<saml:AttributeValue>1</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="urn:test:EntityConcernedSubID">
<saml:AttributeValue>1</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
<saml:EncryptedAssertion>
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2009xmlenc11#aes256-gcm"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
<xenc:CipherData>
<xenc:CipherValue>sNLWjwyj/R0oPwSgNnqowahiOwM0YU3YaH3jsH0t2YUDcHkcgouvW5x6YbNdgvGq0ImsNrkjI//0hrL4HvrOX33+DkhCo2FX5+a7UCdftfBfSjvt0houF8z3Zq/XOm6HxBUbWt5MULYpMKMZ9iAY6+raydxk2tFWgnAyHaBfzvU=</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
<ds:RetrievalMethod URI="#Encrypted_KEY_ID" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">self::xenc:CipherValue[@Id="example1"]</ds:XPath>
</ds:Transform>
</ds:Transforms>
</ds:RetrievalMethod>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>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</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</saml:EncryptedAssertion>
</saml:Evidence>
</saml:AuthzDecisionStatement>